@bpmnkit/plugins 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +153 -0
  2. package/dist/ai-bridge/css.d.ts +2 -0
  3. package/dist/ai-bridge/css.js +366 -0
  4. package/dist/ai-bridge/index.d.ts +44 -0
  5. package/dist/ai-bridge/index.js +132 -0
  6. package/dist/ai-bridge/panel.d.ts +27 -0
  7. package/dist/ai-bridge/panel.js +715 -0
  8. package/dist/ascii-view/css.d.ts +4 -0
  9. package/dist/ascii-view/css.js +83 -0
  10. package/dist/ascii-view/index.d.ts +25 -0
  11. package/dist/ascii-view/index.js +92 -0
  12. package/dist/command-palette/css.d.ts +4 -0
  13. package/dist/command-palette/css.js +157 -0
  14. package/dist/command-palette/index.d.ts +53 -0
  15. package/dist/command-palette/index.js +378 -0
  16. package/dist/command-palette-editor/index.d.ts +35 -0
  17. package/dist/command-palette-editor/index.js +60 -0
  18. package/dist/config-panel/css.d.ts +4 -0
  19. package/dist/config-panel/css.js +719 -0
  20. package/dist/config-panel/index.d.ts +36 -0
  21. package/dist/config-panel/index.js +73 -0
  22. package/dist/config-panel/renderer.d.ts +127 -0
  23. package/dist/config-panel/renderer.js +1162 -0
  24. package/dist/config-panel/types.d.ts +118 -0
  25. package/dist/config-panel/types.js +2 -0
  26. package/dist/config-panel-bpmn/index.d.ts +58 -0
  27. package/dist/config-panel-bpmn/index.js +1320 -0
  28. package/dist/config-panel-bpmn/template-engine.d.ts +17 -0
  29. package/dist/config-panel-bpmn/template-engine.js +307 -0
  30. package/dist/config-panel-bpmn/template-to-service-task.d.ts +30 -0
  31. package/dist/config-panel-bpmn/template-to-service-task.js +92 -0
  32. package/dist/config-panel-bpmn/template-types.d.ts +126 -0
  33. package/dist/config-panel-bpmn/template-types.js +6 -0
  34. package/dist/config-panel-bpmn/templates/generated.d.ts +3 -0
  35. package/dist/config-panel-bpmn/templates/generated.js +57865 -0
  36. package/dist/config-panel-bpmn/util.d.ts +46 -0
  37. package/dist/config-panel-bpmn/util.js +195 -0
  38. package/dist/dmn-editor/css.d.ts +3 -0
  39. package/dist/dmn-editor/css.js +667 -0
  40. package/dist/dmn-editor/dmn-editor.d.ts +34 -0
  41. package/dist/dmn-editor/dmn-editor.js +646 -0
  42. package/dist/dmn-editor/drd-canvas.d.ts +85 -0
  43. package/dist/dmn-editor/drd-canvas.js +1087 -0
  44. package/dist/dmn-editor/index.d.ts +3 -0
  45. package/dist/dmn-editor/index.js +2 -0
  46. package/dist/dmn-viewer/css.d.ts +3 -0
  47. package/dist/dmn-viewer/css.js +151 -0
  48. package/dist/dmn-viewer/dmn-viewer.d.ts +30 -0
  49. package/dist/dmn-viewer/dmn-viewer.js +160 -0
  50. package/dist/dmn-viewer/feel.d.ts +3 -0
  51. package/dist/dmn-viewer/feel.js +2 -0
  52. package/dist/dmn-viewer/index.d.ts +14 -0
  53. package/dist/dmn-viewer/index.js +12 -0
  54. package/dist/element-docs/content.d.ts +20 -0
  55. package/dist/element-docs/content.js +1032 -0
  56. package/dist/element-docs/index.d.ts +7 -0
  57. package/dist/element-docs/index.js +643 -0
  58. package/dist/feel-playground/css.d.ts +4 -0
  59. package/dist/feel-playground/css.js +292 -0
  60. package/dist/feel-playground/index.d.ts +4 -0
  61. package/dist/feel-playground/index.js +3 -0
  62. package/dist/feel-playground/playground-plugin.d.ts +14 -0
  63. package/dist/feel-playground/playground-plugin.js +292 -0
  64. package/dist/form-editor/css.d.ts +3 -0
  65. package/dist/form-editor/css.js +703 -0
  66. package/dist/form-editor/form-editor.d.ts +48 -0
  67. package/dist/form-editor/form-editor.js +1039 -0
  68. package/dist/form-editor/index.d.ts +3 -0
  69. package/dist/form-editor/index.js +2 -0
  70. package/dist/form-viewer/css.d.ts +3 -0
  71. package/dist/form-viewer/css.js +303 -0
  72. package/dist/form-viewer/form-viewer.d.ts +29 -0
  73. package/dist/form-viewer/form-viewer.js +376 -0
  74. package/dist/form-viewer/index.d.ts +12 -0
  75. package/dist/form-viewer/index.js +11 -0
  76. package/dist/history/checkpoint.d.ts +10 -0
  77. package/dist/history/checkpoint.js +92 -0
  78. package/dist/history/css.d.ts +2 -0
  79. package/dist/history/css.js +130 -0
  80. package/dist/history/history-panel.d.ts +12 -0
  81. package/dist/history/history-panel.js +142 -0
  82. package/dist/history/index.d.ts +5 -0
  83. package/dist/history/index.js +3 -0
  84. package/dist/main-menu/css.d.ts +4 -0
  85. package/dist/main-menu/css.js +272 -0
  86. package/dist/main-menu/index.d.ts +59 -0
  87. package/dist/main-menu/index.js +285 -0
  88. package/dist/minimap/css.d.ts +10 -0
  89. package/dist/minimap/css.js +54 -0
  90. package/dist/minimap/index.d.ts +38 -0
  91. package/dist/minimap/index.js +69 -0
  92. package/dist/minimap/minimap.d.ts +35 -0
  93. package/dist/minimap/minimap.js +166 -0
  94. package/dist/optimize/index.d.ts +21 -0
  95. package/dist/optimize/index.js +304 -0
  96. package/dist/process-runner/css.d.ts +2 -0
  97. package/dist/process-runner/css.js +467 -0
  98. package/dist/process-runner/index.d.ts +56 -0
  99. package/dist/process-runner/index.js +549 -0
  100. package/dist/storage/auto-save.d.ts +11 -0
  101. package/dist/storage/auto-save.js +44 -0
  102. package/dist/storage/css.d.ts +2 -0
  103. package/dist/storage/css.js +2 -0
  104. package/dist/storage/db.d.ts +99 -0
  105. package/dist/storage/db.js +116 -0
  106. package/dist/storage/dialog.d.ts +15 -0
  107. package/dist/storage/dialog.js +201 -0
  108. package/dist/storage/export.d.ts +3 -0
  109. package/dist/storage/export.js +137 -0
  110. package/dist/storage/index.d.ts +44 -0
  111. package/dist/storage/index.js +201 -0
  112. package/dist/storage/sidebar.d.ts +2 -0
  113. package/dist/storage/sidebar.js +2 -0
  114. package/dist/storage/storage-api.d.ts +76 -0
  115. package/dist/storage/storage-api.js +342 -0
  116. package/dist/storage/types.d.ts +44 -0
  117. package/dist/storage/types.js +2 -0
  118. package/dist/storage-tabs-bridge/index.d.ts +96 -0
  119. package/dist/storage-tabs-bridge/index.js +502 -0
  120. package/dist/tabs/css.d.ts +3 -0
  121. package/dist/tabs/css.js +642 -0
  122. package/dist/tabs/file-resolver.d.ts +71 -0
  123. package/dist/tabs/file-resolver.js +46 -0
  124. package/dist/tabs/index.d.ts +15 -0
  125. package/dist/tabs/index.js +13 -0
  126. package/dist/tabs/tabs-plugin.d.ts +250 -0
  127. package/dist/tabs/tabs-plugin.js +1119 -0
  128. package/dist/token-highlight/css.d.ts +2 -0
  129. package/dist/token-highlight/css.js +81 -0
  130. package/dist/token-highlight/index.d.ts +44 -0
  131. package/dist/token-highlight/index.js +164 -0
  132. package/dist/watermark/css.d.ts +10 -0
  133. package/dist/watermark/css.js +54 -0
  134. package/dist/watermark/index.d.ts +48 -0
  135. package/dist/watermark/index.js +64 -0
  136. package/dist/zoom-controls/css.d.ts +4 -0
  137. package/dist/zoom-controls/css.js +48 -0
  138. package/dist/zoom-controls/index.d.ts +36 -0
  139. package/dist/zoom-controls/index.js +111 -0
  140. package/package.json +121 -0
@@ -0,0 +1,1087 @@
1
+ // ── Constants ─────────────────────────────────────────────────────────────────
2
+ const SVG_NS = "http://www.w3.org/2000/svg";
3
+ const GRID_SIZE = 20;
4
+ let _instanceCounter = 0;
5
+ const NODE_SIZE = {
6
+ decision: { width: 180, height: 80 },
7
+ inputData: { width: 125, height: 45 },
8
+ knowledgeSource: { width: 100, height: 63 },
9
+ businessKnowledgeModel: { width: 135, height: 46 },
10
+ textAnnotation: { width: 100, height: 80 },
11
+ };
12
+ // ── Icons ─────────────────────────────────────────────────────────────────────
13
+ const IC = {
14
+ decision: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="1" width="26" height="16" rx="1.5"/></svg>`,
15
+ inputData: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><rect x="1" y="1" width="26" height="14" rx="7"/></svg>`,
16
+ knowledgeSource: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 20" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M1,1 L23,1 L23,15 C18,21 6,11 1,17 Z"/></svg>`,
17
+ businessKnowledgeModel: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6,1 L25,1 L25,17 L1,17 L1,6 Z"/></svg>`,
18
+ textAnnotation: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8,1 L1,1 L1,17 L8,17"/></svg>`,
19
+ connect: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2,7 L16,7 M12,3 L16,7 L12,11"/></svg>`,
20
+ editTable: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="1" width="18" height="14" rx="1"/><line x1="1" y1="6" x2="19" y2="6"/><line x1="8" y1="6" x2="8" y2="15"/></svg>`,
21
+ trash: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="1,4 15,4"/><path d="M3,4 L3,15 L13,15 L13,4"/><path d="M6,1 L10,1"/></svg>`,
22
+ };
23
+ // ── Connection rules for quick-add ────────────────────────────────────────────
24
+ const QUICK_ADD_TARGETS = {
25
+ decision: ["decision", "textAnnotation"],
26
+ inputData: ["decision", "textAnnotation"],
27
+ knowledgeSource: ["decision", "knowledgeSource", "businessKnowledgeModel", "textAnnotation"],
28
+ businessKnowledgeModel: ["decision", "businessKnowledgeModel", "textAnnotation"],
29
+ };
30
+ const QUICK_ADD_LABELS = {
31
+ decision: "Decision",
32
+ inputData: "Input Data",
33
+ knowledgeSource: "Knowledge Source",
34
+ businessKnowledgeModel: "BKM",
35
+ textAnnotation: "Annotation",
36
+ };
37
+ // ── Geometry helpers ──────────────────────────────────────────────────────────
38
+ function rectEdgePoint(cx, cy, w, h, tx, ty) {
39
+ const dx = tx - cx;
40
+ const dy = ty - cy;
41
+ if (dx === 0 && dy === 0)
42
+ return { x: cx, y: cy };
43
+ const hw = w / 2;
44
+ const hh = h / 2;
45
+ const sx = hw / Math.abs(dx);
46
+ const sy = hh / Math.abs(dy);
47
+ const scale = Math.min(sx, sy);
48
+ return { x: cx + dx * scale, y: cy + dy * scale };
49
+ }
50
+ function computeEdgePath(src, tgt, waypoints) {
51
+ if (waypoints.length >= 2) {
52
+ const first = waypoints[0];
53
+ if (!first)
54
+ return "";
55
+ let d = `M ${first.x},${first.y}`;
56
+ for (let i = 1; i < waypoints.length; i++) {
57
+ const wp = waypoints[i];
58
+ if (wp)
59
+ d += ` L ${wp.x},${wp.y}`;
60
+ }
61
+ return d;
62
+ }
63
+ const sx = src.x + src.width / 2;
64
+ const sy = src.y + src.height / 2;
65
+ const tx = tgt.x + tgt.width / 2;
66
+ const ty = tgt.y + tgt.height / 2;
67
+ const start = rectEdgePoint(sx, sy, src.width, src.height, tx, ty);
68
+ const end = rectEdgePoint(tx, ty, tgt.width, tgt.height, sx, sy);
69
+ return `M ${start.x},${start.y} L ${end.x},${end.y}`;
70
+ }
71
+ // ── SVG helpers ───────────────────────────────────────────────────────────────
72
+ function svgEl(tag) {
73
+ return document.createElementNS(SVG_NS, tag);
74
+ }
75
+ function setAttrs(el, attrs) {
76
+ for (const [k, v] of Object.entries(attrs))
77
+ el.setAttribute(k, String(v));
78
+ }
79
+ function cls(el, names) {
80
+ el.setAttribute("class", names);
81
+ }
82
+ function uid() {
83
+ return Math.random().toString(36).slice(2, 10);
84
+ }
85
+ // ── DRD Canvas ────────────────────────────────────────────────────────────────
86
+ export class DrdCanvas {
87
+ _defs;
88
+ _root;
89
+ _svg;
90
+ _vpGroup;
91
+ _edgesLayer;
92
+ _nodesLayer;
93
+ _svgDefsEl;
94
+ _gridPattern = null;
95
+ _guidesLayer;
96
+ _bottomBar;
97
+ _ctxBar;
98
+ _ctxBarNodeId = null;
99
+ _vpX = 40;
100
+ _vpY = 40;
101
+ _scale = 1;
102
+ _selectedIds = new Set();
103
+ _panStart = null;
104
+ _dragNode = null;
105
+ _connectMode = false;
106
+ _connectSourceId = null;
107
+ _onChange;
108
+ _onDecisionOpen;
109
+ _cleanups = [];
110
+ constructor(options) {
111
+ _instanceCounter++;
112
+ this._defs = options.defs;
113
+ this._onChange = options.onChange ?? (() => undefined);
114
+ this._onDecisionOpen = options.onDecisionOpen ?? (() => undefined);
115
+ // Root — position: relative so absolute children are positioned inside
116
+ this._root = document.createElement("div");
117
+ this._root.className = "drd-root";
118
+ // SVG — fills root absolutely
119
+ this._svg = svgEl("svg");
120
+ cls(this._svg, "drd-svg");
121
+ this._root.appendChild(this._svg);
122
+ // Defs (markers + grid pattern)
123
+ this._svgDefsEl = svgEl("defs");
124
+ this._svg.appendChild(this._svgDefsEl);
125
+ this._buildMarkers();
126
+ this._buildGrid();
127
+ // Viewport group (contains edges + nodes)
128
+ this._vpGroup = svgEl("g");
129
+ this._svg.appendChild(this._vpGroup);
130
+ this._edgesLayer = svgEl("g");
131
+ cls(this._edgesLayer, "drd-edges");
132
+ this._vpGroup.appendChild(this._edgesLayer);
133
+ this._nodesLayer = svgEl("g");
134
+ cls(this._nodesLayer, "drd-nodes");
135
+ this._vpGroup.appendChild(this._nodesLayer);
136
+ this._guidesLayer = svgEl("g");
137
+ cls(this._guidesLayer, "drd-guides");
138
+ this._vpGroup.appendChild(this._guidesLayer);
139
+ // Floating toolbars
140
+ this._bottomBar = this._buildBottomBar();
141
+ this._root.appendChild(this._bottomBar);
142
+ this._ctxBar = document.createElement("div");
143
+ this._ctxBar.className = "drd-bar drd-ctx-bar";
144
+ this._ctxBar.style.display = "none";
145
+ this._root.appendChild(this._ctxBar);
146
+ options.container.appendChild(this._root);
147
+ this._bindEvents();
148
+ this._ensureLayout();
149
+ this._render();
150
+ }
151
+ setDefs(defs) {
152
+ this._defs = defs;
153
+ this._ensureLayout();
154
+ this._render();
155
+ }
156
+ destroy() {
157
+ for (const fn of this._cleanups)
158
+ fn();
159
+ this._cleanups.length = 0;
160
+ this._root.remove();
161
+ }
162
+ // ── Dot-grid background ────────────────────────────────────────────────────
163
+ _buildGrid() {
164
+ const patternId = `drd-grid-${_instanceCounter}`;
165
+ const pattern = svgEl("pattern");
166
+ setAttrs(pattern, {
167
+ id: patternId,
168
+ width: GRID_SIZE,
169
+ height: GRID_SIZE,
170
+ patternUnits: "userSpaceOnUse",
171
+ });
172
+ const dot = svgEl("circle");
173
+ setAttrs(dot, { cx: 1, cy: 1, r: 1, fill: "var(--drd-grid, rgba(0,0,0,0.12))" });
174
+ pattern.appendChild(dot);
175
+ this._svgDefsEl.appendChild(pattern);
176
+ this._gridPattern = pattern;
177
+ // Background rect filled with the pattern (before vpGroup so it's behind nodes)
178
+ const bg = svgEl("rect");
179
+ setAttrs(bg, { width: "100%", height: "100%", fill: `url(#${patternId})` });
180
+ bg.setAttribute("pointer-events", "none");
181
+ this._svg.insertBefore(bg, this._vpGroup);
182
+ }
183
+ // ── Auto-layout for nodes without diagram positions ────────────────────────
184
+ _ensureLayout() {
185
+ const defs = this._defs;
186
+ if (!defs.diagram)
187
+ defs.diagram = { shapes: [], edges: [] };
188
+ const diagram = defs.diagram;
189
+ const existingIds = new Set(diagram.shapes.map((s) => s.dmnElementRef));
190
+ const allNodes = this._buildNodes();
191
+ const unplaced = allNodes.filter((n) => !existingIds.has(n.id));
192
+ if (unplaced.length === 0)
193
+ return;
194
+ const maxX = diagram.shapes.reduce((m, s) => Math.max(m, s.bounds.x + s.bounds.width), 0);
195
+ const startX = diagram.shapes.length > 0 ? maxX + 80 : 80;
196
+ unplaced.forEach((n, i) => {
197
+ const col = i % 3;
198
+ const row = Math.floor(i / 3);
199
+ diagram.shapes.push({
200
+ dmnElementRef: n.id,
201
+ bounds: {
202
+ x: startX + col * 220,
203
+ y: 80 + row * 120,
204
+ width: NODE_SIZE[n.type].width,
205
+ height: NODE_SIZE[n.type].height,
206
+ },
207
+ });
208
+ });
209
+ }
210
+ // ── Data extraction ────────────────────────────────────────────────────────
211
+ _buildNodes() {
212
+ const defs = this._defs;
213
+ const shapesMap = new Map((defs.diagram?.shapes ?? []).map((s) => [s.dmnElementRef, s]));
214
+ const nodes = [];
215
+ const push = (id, type, label) => {
216
+ const shape = shapesMap.get(id);
217
+ const size = NODE_SIZE[type];
218
+ nodes.push({
219
+ id,
220
+ type,
221
+ label,
222
+ x: shape?.bounds.x ?? 80,
223
+ y: shape?.bounds.y ?? 80,
224
+ width: shape?.bounds.width ?? size.width,
225
+ height: shape?.bounds.height ?? size.height,
226
+ });
227
+ };
228
+ for (const d of defs.decisions)
229
+ push(d.id, "decision", d.name ?? d.id);
230
+ for (const id of defs.inputData)
231
+ push(id.id, "inputData", id.name ?? id.id);
232
+ for (const ks of defs.knowledgeSources)
233
+ push(ks.id, "knowledgeSource", ks.name ?? ks.id);
234
+ for (const bkm of defs.businessKnowledgeModels)
235
+ push(bkm.id, "businessKnowledgeModel", bkm.name ?? bkm.id);
236
+ for (const ann of defs.textAnnotations)
237
+ push(ann.id, "textAnnotation", ann.text ?? ann.id);
238
+ return nodes;
239
+ }
240
+ _buildEdges() {
241
+ const defs = this._defs;
242
+ const waypointsMap = new Map((defs.diagram?.edges ?? []).map((e) => [e.dmnElementRef, e.waypoints]));
243
+ const edges = [];
244
+ const push = (id, type, sourceId, targetId) => {
245
+ if (!sourceId || !targetId)
246
+ return;
247
+ edges.push({ id, type, sourceId, targetId, waypoints: waypointsMap.get(id) ?? [] });
248
+ };
249
+ for (const d of defs.decisions) {
250
+ for (const req of d.informationRequirements)
251
+ push(req.id, "informationRequirement", req.requiredDecision ?? req.requiredInput ?? "", d.id);
252
+ for (const req of d.knowledgeRequirements)
253
+ push(req.id, "knowledgeRequirement", req.requiredKnowledge, d.id);
254
+ for (const req of d.authorityRequirements)
255
+ push(req.id, "authorityRequirement", req.requiredAuthority ?? req.requiredDecision ?? req.requiredInput ?? "", d.id);
256
+ }
257
+ for (const ks of defs.knowledgeSources) {
258
+ for (const req of ks.authorityRequirements)
259
+ push(req.id, "authorityRequirement", req.requiredAuthority ?? req.requiredDecision ?? req.requiredInput ?? "", ks.id);
260
+ }
261
+ for (const bkm of defs.businessKnowledgeModels) {
262
+ for (const req of bkm.knowledgeRequirements)
263
+ push(req.id, "knowledgeRequirement", req.requiredKnowledge, bkm.id);
264
+ for (const req of bkm.authorityRequirements)
265
+ push(req.id, "authorityRequirement", req.requiredAuthority ?? req.requiredDecision ?? req.requiredInput ?? "", bkm.id);
266
+ }
267
+ for (const assoc of defs.associations)
268
+ push(assoc.id, "association", assoc.sourceRef, assoc.targetRef);
269
+ return edges;
270
+ }
271
+ // ── Rendering ─────────────────────────────────────────────────────────────
272
+ _buildMarkers() {
273
+ const mk = (id, w, h, rx, ry) => {
274
+ const m = svgEl("marker");
275
+ setAttrs(m, { id, markerWidth: w, markerHeight: h, refX: rx, refY: ry, orient: "auto" });
276
+ return m;
277
+ };
278
+ // Filled arrow — InformationRequirement
279
+ const infoM = mk("drd-info-arrow", 10, 8, 9, 4);
280
+ const infoP = svgEl("path");
281
+ setAttrs(infoP, { d: "M 0,0 L 9,4 L 0,8 Z" });
282
+ cls(infoP, "drd-marker-fill");
283
+ infoM.appendChild(infoP);
284
+ // Open V arrow — KnowledgeRequirement
285
+ const knowM = mk("drd-know-arrow", 12, 8, 10, 4);
286
+ const knowPL = svgEl("polyline");
287
+ setAttrs(knowPL, { points: "0,0 8,4 0,8", fill: "none", "stroke-width": 1.5 });
288
+ cls(knowPL, "drd-marker-stroke");
289
+ knowM.appendChild(knowPL);
290
+ // Circle — AuthorityRequirement source
291
+ const authM = mk("drd-auth-circle", 10, 10, 5, 5);
292
+ const authC = svgEl("circle");
293
+ setAttrs(authC, { cx: 5, cy: 5, r: 4, fill: "none", "stroke-width": 1.5 });
294
+ cls(authC, "drd-marker-stroke");
295
+ authM.appendChild(authC);
296
+ // Open arrow — Association
297
+ const assocM = mk("drd-assoc-arrow", 10, 8, 9, 4);
298
+ const assocPL = svgEl("polyline");
299
+ setAttrs(assocPL, { points: "0,0 8,4 0,8", fill: "none", "stroke-width": 1.5 });
300
+ cls(assocPL, "drd-marker-assoc");
301
+ assocM.appendChild(assocPL);
302
+ for (const m of [infoM, knowM, authM, assocM])
303
+ this._svgDefsEl.appendChild(m);
304
+ }
305
+ _render() {
306
+ this._edgesLayer.innerHTML = "";
307
+ this._nodesLayer.innerHTML = "";
308
+ this._updateTransform();
309
+ // Toggle crosshair cursor when in connect mode
310
+ this._root.classList.toggle("drd-connect-mode", this._connectMode);
311
+ const nodes = this._buildNodes();
312
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
313
+ const edges = this._buildEdges();
314
+ for (const edge of edges) {
315
+ const src = nodeMap.get(edge.sourceId);
316
+ const tgt = nodeMap.get(edge.targetId);
317
+ if (src && tgt)
318
+ this._edgesLayer.appendChild(this._renderEdge(edge, src, tgt));
319
+ }
320
+ for (const node of nodes)
321
+ this._nodesLayer.appendChild(this._renderNode(node));
322
+ // Update contextual toolbar
323
+ if (this._selectedIds.size === 1) {
324
+ const id = [...this._selectedIds][0];
325
+ const n = id ? nodeMap.get(id) : undefined;
326
+ if (n) {
327
+ this._showCtxBar(n);
328
+ }
329
+ else {
330
+ this._hideCtxBar();
331
+ }
332
+ }
333
+ else {
334
+ this._hideCtxBar();
335
+ }
336
+ }
337
+ _renderNode(n) {
338
+ const g = svgEl("g");
339
+ const selected = this._selectedIds.has(n.id);
340
+ const connectSrc = this._connectMode && this._connectSourceId === n.id;
341
+ const classes = ["drd-node", `drd-node--${n.type}`];
342
+ if (selected)
343
+ classes.push("drd-node--selected");
344
+ if (connectSrc)
345
+ classes.push("drd-node--connect-src");
346
+ cls(g, classes.join(" "));
347
+ g.dataset.id = n.id;
348
+ setAttrs(g, { transform: `translate(${n.x},${n.y})` });
349
+ g.appendChild(this._makeNodeShape(n));
350
+ const fo = svgEl("foreignObject");
351
+ setAttrs(fo, { x: 0, y: 0, width: n.width, height: n.height });
352
+ const div = document.createElement("div");
353
+ div.className = "drd-label";
354
+ div.style.cssText = `width:${n.width}px;height:${n.height}px;`;
355
+ div.textContent = n.label;
356
+ fo.appendChild(div);
357
+ g.appendChild(fo);
358
+ if (this._connectMode) {
359
+ const hi = svgEl("rect");
360
+ setAttrs(hi, { x: -3, y: -3, width: n.width + 6, height: n.height + 6, rx: 4, ry: 4 });
361
+ cls(hi, "drd-connect-hi");
362
+ g.appendChild(hi);
363
+ }
364
+ g.addEventListener("mousedown", (e) => this._onNodeMouseDown(e, n));
365
+ g.addEventListener("dblclick", (e) => this._onNodeDblClick(e, n));
366
+ return g;
367
+ }
368
+ _makeNodeShape(n) {
369
+ const { width: w, height: h } = n;
370
+ let el;
371
+ if (n.type === "decision") {
372
+ const r = svgEl("rect");
373
+ setAttrs(r, { x: 0, y: 0, width: w, height: h, rx: 2, ry: 2 });
374
+ el = r;
375
+ }
376
+ else if (n.type === "inputData") {
377
+ const r = svgEl("rect");
378
+ setAttrs(r, { x: 0, y: 0, width: w, height: h, rx: h / 2, ry: h / 2 });
379
+ el = r;
380
+ }
381
+ else if (n.type === "knowledgeSource") {
382
+ const wave = h * 0.18;
383
+ const p = svgEl("path");
384
+ setAttrs(p, {
385
+ d: `M 0,0 L ${w},0 L ${w},${h - wave} C ${w * 0.75},${h + wave * 0.5} ${w * 0.25},${h - wave * 2.5} 0,${h - wave} Z`,
386
+ });
387
+ el = p;
388
+ }
389
+ else if (n.type === "businessKnowledgeModel") {
390
+ const clip = 14;
391
+ const p = svgEl("path");
392
+ setAttrs(p, { d: `M ${clip},0 L ${w},0 L ${w},${h} L 0,${h} L 0,${clip} Z` });
393
+ el = p;
394
+ }
395
+ else {
396
+ // textAnnotation — open bracket
397
+ const p = svgEl("path");
398
+ const inset = 16;
399
+ setAttrs(p, {
400
+ d: `M ${inset},0 L 0,0 L 0,${h} L ${inset},${h}`,
401
+ fill: "none",
402
+ });
403
+ el = p;
404
+ }
405
+ cls(el, "drd-shape");
406
+ return el;
407
+ }
408
+ _renderEdge(edge, src, tgt) {
409
+ const g = svgEl("g");
410
+ const selected = this._selectedIds.has(edge.id);
411
+ const classes = ["drd-edge", `drd-edge--${edge.type}`];
412
+ if (selected)
413
+ classes.push("drd-edge--selected");
414
+ cls(g, classes.join(" "));
415
+ g.dataset.id = edge.id;
416
+ const d = computeEdgePath(src, tgt, edge.waypoints);
417
+ const hit = svgEl("path");
418
+ setAttrs(hit, { d, stroke: "transparent", "stroke-width": 12, fill: "none" });
419
+ g.appendChild(hit);
420
+ const line = svgEl("path");
421
+ setAttrs(line, { d, fill: "none", "stroke-width": 1.5, "stroke-linecap": "round" });
422
+ cls(line, "drd-edge-line");
423
+ if (edge.type === "informationRequirement") {
424
+ line.setAttribute("marker-end", "url(#drd-info-arrow)");
425
+ }
426
+ else if (edge.type === "knowledgeRequirement") {
427
+ line.setAttribute("stroke-dasharray", "6,3");
428
+ line.setAttribute("marker-end", "url(#drd-know-arrow)");
429
+ }
430
+ else if (edge.type === "authorityRequirement") {
431
+ line.setAttribute("stroke-dasharray", "6,3");
432
+ line.setAttribute("marker-start", "url(#drd-auth-circle)");
433
+ }
434
+ else {
435
+ line.setAttribute("stroke-dasharray", "3,4");
436
+ line.setAttribute("marker-end", "url(#drd-assoc-arrow)");
437
+ }
438
+ g.appendChild(line);
439
+ g.addEventListener("mousedown", (e) => {
440
+ e.stopPropagation();
441
+ if (this._connectMode)
442
+ return;
443
+ this._selectedIds.clear();
444
+ this._selectedIds.add(edge.id);
445
+ this._render();
446
+ });
447
+ return g;
448
+ }
449
+ _updateTransform() {
450
+ const t = `translate(${this._vpX},${this._vpY}) scale(${this._scale})`;
451
+ this._vpGroup.setAttribute("transform", t);
452
+ if (this._gridPattern) {
453
+ this._gridPattern.setAttribute("patternTransform", t);
454
+ }
455
+ this._updateCtxBarPosition();
456
+ }
457
+ // ── Snap alignment ─────────────────────────────────────────────────────────
458
+ _computeSnap(cx, cy, w, h, dragId) {
459
+ const thr = 8 / this._scale;
460
+ const shapes = (this._defs.diagram?.shapes ?? []).filter((s) => s.dmnElementRef !== dragId);
461
+ const dxs = [cx, cx + w / 2, cx + w];
462
+ const dys = [cy, cy + h / 2, cy + h];
463
+ let bestX = null;
464
+ let bestY = null;
465
+ for (const s of shapes) {
466
+ const { x, y, width, height } = s.bounds;
467
+ const sxs = [x, x + width / 2, x + width];
468
+ const sys = [y, y + height / 2, y + height];
469
+ for (const da of dxs) {
470
+ for (const sa of sxs) {
471
+ const d = sa - da;
472
+ if (Math.abs(d) < thr && (bestX === null || Math.abs(d) < Math.abs(bestX.delta))) {
473
+ bestX = { delta: d, guidePos: sa };
474
+ }
475
+ }
476
+ }
477
+ for (const da of dys) {
478
+ for (const sa of sys) {
479
+ const d = sa - da;
480
+ if (Math.abs(d) < thr && (bestY === null || Math.abs(d) < Math.abs(bestY.delta))) {
481
+ bestY = { delta: d, guidePos: sa };
482
+ }
483
+ }
484
+ }
485
+ }
486
+ const guides = [];
487
+ if (bestX)
488
+ guides.push({ type: "v", pos: bestX.guidePos });
489
+ if (bestY)
490
+ guides.push({ type: "h", pos: bestY.guidePos });
491
+ return { adjX: bestX?.delta ?? 0, adjY: bestY?.delta ?? 0, guides };
492
+ }
493
+ _renderGuides(guides) {
494
+ this._guidesLayer.innerHTML = "";
495
+ for (const g of guides) {
496
+ const line = svgEl("line");
497
+ cls(line, "drd-align-guide");
498
+ if (g.type === "v") {
499
+ setAttrs(line, { x1: g.pos, y1: -2000, x2: g.pos, y2: 2000 });
500
+ }
501
+ else {
502
+ setAttrs(line, { x1: -2000, y1: g.pos, x2: 2000, y2: g.pos });
503
+ }
504
+ this._guidesLayer.appendChild(line);
505
+ }
506
+ }
507
+ _clearGuides() {
508
+ this._guidesLayer.innerHTML = "";
509
+ }
510
+ // ── Bottom toolbar ─────────────────────────────────────────────────────────
511
+ _buildBottomBar() {
512
+ const bar = document.createElement("div");
513
+ bar.className = "drd-bar drd-bottom-bar";
514
+ const btn = (icon, title, onClick) => {
515
+ const b = document.createElement("button");
516
+ b.type = "button";
517
+ b.className = "drd-bar-btn";
518
+ b.innerHTML = icon;
519
+ b.title = title;
520
+ b.addEventListener("click", onClick);
521
+ return b;
522
+ };
523
+ const sep = () => {
524
+ const d = document.createElement("div");
525
+ d.className = "drd-bar-sep";
526
+ return d;
527
+ };
528
+ bar.append(btn(IC.decision, "Add Decision", () => this._addNode("decision")), btn(IC.inputData, "Add Input Data", () => this._addNode("inputData")), btn(IC.knowledgeSource, "Add Knowledge Source", () => this._addNode("knowledgeSource")), btn(IC.businessKnowledgeModel, "Add Business Knowledge Model", () => this._addNode("businessKnowledgeModel")), sep(), btn(IC.textAnnotation, "Add Text Annotation", () => this._addNode("textAnnotation")));
529
+ return bar;
530
+ }
531
+ // ── Contextual toolbar ─────────────────────────────────────────────────────
532
+ _showCtxBar(n) {
533
+ this._ctxBarNodeId = n.id;
534
+ this._ctxBar.innerHTML = "";
535
+ const barBtn = (icon, title, onClick, active = false) => {
536
+ const b = document.createElement("button");
537
+ b.type = "button";
538
+ b.className = active ? "drd-bar-btn active" : "drd-bar-btn";
539
+ b.innerHTML = icon;
540
+ b.title = title;
541
+ b.addEventListener("click", (e) => {
542
+ e.stopPropagation();
543
+ onClick();
544
+ });
545
+ return b;
546
+ };
547
+ const sep = () => {
548
+ const d = document.createElement("div");
549
+ d.className = "drd-bar-sep";
550
+ return d;
551
+ };
552
+ // "Edit Table" — Decision nodes only
553
+ if (n.type === "decision") {
554
+ this._ctxBar.append(barBtn(IC.editTable, "Edit decision table (double-click)", () => this._onDecisionOpen(n.id)), sep());
555
+ }
556
+ // "Connect" — start connect mode from this node
557
+ const isConnectSrc = this._connectMode && this._connectSourceId === n.id;
558
+ this._ctxBar.appendChild(barBtn(IC.connect, isConnectSrc ? "Cancel connect (Escape)" : "Connect to another element", () => {
559
+ if (this._connectMode && this._connectSourceId === n.id) {
560
+ this._connectMode = false;
561
+ this._connectSourceId = null;
562
+ }
563
+ else {
564
+ this._connectMode = true;
565
+ this._connectSourceId = n.id;
566
+ }
567
+ this._render();
568
+ }, isConnectSrc));
569
+ // Quick-add connected nodes
570
+ const quickTargets = QUICK_ADD_TARGETS[n.type];
571
+ if (quickTargets && quickTargets.length > 0) {
572
+ this._ctxBar.appendChild(sep());
573
+ for (const targetType of quickTargets) {
574
+ const label = QUICK_ADD_LABELS[targetType] ?? targetType;
575
+ this._ctxBar.appendChild(barBtn(IC[targetType], `Add connected ${label}`, () => this._addConnectedNode(n.id, targetType)));
576
+ }
577
+ }
578
+ // "Delete"
579
+ this._ctxBar.append(sep(), barBtn(IC.trash, "Delete element (Delete key)", () => {
580
+ this._deleteElement(n.id);
581
+ this._selectedIds.clear();
582
+ this._render();
583
+ this._onChange();
584
+ }));
585
+ this._updateCtxBarPosition();
586
+ this._ctxBar.style.display = "flex";
587
+ }
588
+ _hideCtxBar() {
589
+ this._ctxBarNodeId = null;
590
+ this._ctxBar.style.display = "none";
591
+ }
592
+ _updateCtxBarPosition() {
593
+ if (!this._ctxBarNodeId)
594
+ return;
595
+ const shape = this._defs.diagram?.shapes.find((s) => s.dmnElementRef === this._ctxBarNodeId);
596
+ if (!shape)
597
+ return;
598
+ const { x, y, width, height } = shape.bounds;
599
+ const cx = (x + width / 2) * this._scale + this._vpX;
600
+ const top = (y + height) * this._scale + this._vpY + 8;
601
+ this._ctxBar.style.left = `${cx}px`;
602
+ this._ctxBar.style.top = `${top}px`;
603
+ }
604
+ // ── Node creation ──────────────────────────────────────────────────────────
605
+ _addNode(type) {
606
+ const id = `${type}_${uid()}`;
607
+ const size = NODE_SIZE[type];
608
+ const defs = this._defs;
609
+ const cx = (this._svg.clientWidth / 2 - this._vpX) / this._scale;
610
+ const cy = (this._svg.clientHeight / 2 - this._vpY) / this._scale;
611
+ if (!defs.diagram)
612
+ defs.diagram = { shapes: [], edges: [] };
613
+ defs.diagram.shapes.push({
614
+ dmnElementRef: id,
615
+ bounds: {
616
+ x: cx - size.width / 2,
617
+ y: cy - size.height / 2,
618
+ width: size.width,
619
+ height: size.height,
620
+ },
621
+ });
622
+ switch (type) {
623
+ case "decision":
624
+ defs.decisions.push({
625
+ id,
626
+ name: "Decision",
627
+ informationRequirements: [],
628
+ knowledgeRequirements: [],
629
+ authorityRequirements: [],
630
+ });
631
+ break;
632
+ case "inputData":
633
+ defs.inputData.push({ id, name: "Input Data" });
634
+ break;
635
+ case "knowledgeSource":
636
+ defs.knowledgeSources.push({ id, name: "Knowledge Source", authorityRequirements: [] });
637
+ break;
638
+ case "businessKnowledgeModel":
639
+ defs.businessKnowledgeModels.push({
640
+ id,
641
+ name: "BKM",
642
+ knowledgeRequirements: [],
643
+ authorityRequirements: [],
644
+ });
645
+ break;
646
+ case "textAnnotation":
647
+ defs.textAnnotations.push({ id, text: "Note" });
648
+ break;
649
+ }
650
+ this._selectedIds.clear();
651
+ this._selectedIds.add(id);
652
+ this._render();
653
+ this._onChange();
654
+ }
655
+ // ── Smart placement + quick-add ───────────────────────────────────────────
656
+ _smartPlace(sourceId, targetType) {
657
+ const GAP = 60;
658
+ const tSize = NODE_SIZE[targetType];
659
+ const srcShape = this._defs.diagram?.shapes.find((s) => s.dmnElementRef === sourceId);
660
+ if (!srcShape)
661
+ return { x: 80, y: 80 };
662
+ const { x: sx, y: sy, width: sw, height: sh } = srcShape.bounds;
663
+ const allBounds = (this._defs.diagram?.shapes ?? []).map((s) => s.bounds);
664
+ const overlaps = (px, py) => {
665
+ const m = 10;
666
+ for (const b of allBounds) {
667
+ if (px + tSize.width + m > b.x &&
668
+ px - m < b.x + b.width &&
669
+ py + tSize.height + m > b.y &&
670
+ py - m < b.y + b.height) {
671
+ return true;
672
+ }
673
+ }
674
+ return false;
675
+ };
676
+ for (let mult = 1; mult <= 6; mult++) {
677
+ const gap = GAP * mult;
678
+ const candidates = [
679
+ { x: sx + sw + gap, y: sy + sh / 2 - tSize.height / 2 },
680
+ { x: sx + sw / 2 - tSize.width / 2, y: sy + sh + gap },
681
+ { x: sx + sw / 2 - tSize.width / 2, y: sy - tSize.height - gap },
682
+ ];
683
+ for (const c of candidates) {
684
+ if (!overlaps(c.x, c.y))
685
+ return c;
686
+ }
687
+ }
688
+ return { x: sx + sw + GAP * 7, y: sy };
689
+ }
690
+ _addConnectedNode(sourceId, targetType) {
691
+ const id = `${targetType}_${uid()}`;
692
+ const size = NODE_SIZE[targetType];
693
+ const pos = this._smartPlace(sourceId, targetType);
694
+ const defs = this._defs;
695
+ if (!defs.diagram)
696
+ defs.diagram = { shapes: [], edges: [] };
697
+ defs.diagram.shapes.push({
698
+ dmnElementRef: id,
699
+ bounds: { x: pos.x, y: pos.y, width: size.width, height: size.height },
700
+ });
701
+ switch (targetType) {
702
+ case "decision":
703
+ defs.decisions.push({
704
+ id,
705
+ name: "Decision",
706
+ informationRequirements: [],
707
+ knowledgeRequirements: [],
708
+ authorityRequirements: [],
709
+ });
710
+ break;
711
+ case "inputData":
712
+ defs.inputData.push({ id, name: "Input Data" });
713
+ break;
714
+ case "knowledgeSource":
715
+ defs.knowledgeSources.push({ id, name: "Knowledge Source", authorityRequirements: [] });
716
+ break;
717
+ case "businessKnowledgeModel":
718
+ defs.businessKnowledgeModels.push({
719
+ id,
720
+ name: "BKM",
721
+ knowledgeRequirements: [],
722
+ authorityRequirements: [],
723
+ });
724
+ break;
725
+ case "textAnnotation":
726
+ defs.textAnnotations.push({ id, text: "Note" });
727
+ break;
728
+ }
729
+ this._selectedIds.clear();
730
+ this._selectedIds.add(id);
731
+ this._connectNodes(sourceId, id);
732
+ }
733
+ // ── Edge creation ──────────────────────────────────────────────────────────
734
+ _connectNodes(sourceId, targetId) {
735
+ const defs = this._defs;
736
+ if (!defs.diagram)
737
+ defs.diagram = { shapes: [], edges: [] };
738
+ const id = `req_${uid()}`;
739
+ const targetDecision = defs.decisions.find((d) => d.id === targetId);
740
+ const targetKs = defs.knowledgeSources.find((k) => k.id === targetId);
741
+ const targetBkm = defs.businessKnowledgeModels.find((b) => b.id === targetId);
742
+ const targetAnn = defs.textAnnotations.find((a) => a.id === targetId);
743
+ const sourceIsDecision = defs.decisions.some((d) => d.id === sourceId);
744
+ const sourceIsInput = defs.inputData.some((i) => i.id === sourceId);
745
+ const sourceIsBkm = defs.businessKnowledgeModels.some((b) => b.id === sourceId);
746
+ const sourceIsKs = defs.knowledgeSources.some((k) => k.id === sourceId);
747
+ if (targetAnn) {
748
+ const assocId = `assoc_${uid()}`;
749
+ defs.associations.push({ id: assocId, sourceRef: sourceId, targetRef: targetId });
750
+ defs.diagram.edges.push({ dmnElementRef: assocId, waypoints: [] });
751
+ }
752
+ else if (targetDecision) {
753
+ if (sourceIsDecision) {
754
+ targetDecision.informationRequirements.push({ id, requiredDecision: sourceId });
755
+ defs.diagram.edges.push({ dmnElementRef: id, waypoints: [] });
756
+ }
757
+ else if (sourceIsInput) {
758
+ targetDecision.informationRequirements.push({ id, requiredInput: sourceId });
759
+ defs.diagram.edges.push({ dmnElementRef: id, waypoints: [] });
760
+ }
761
+ else if (sourceIsBkm) {
762
+ targetDecision.knowledgeRequirements.push({ id, requiredKnowledge: sourceId });
763
+ defs.diagram.edges.push({ dmnElementRef: id, waypoints: [] });
764
+ }
765
+ else if (sourceIsKs) {
766
+ targetDecision.authorityRequirements.push({ id, requiredAuthority: sourceId });
767
+ defs.diagram.edges.push({ dmnElementRef: id, waypoints: [] });
768
+ }
769
+ else {
770
+ return;
771
+ }
772
+ }
773
+ else if (targetKs && sourceIsKs) {
774
+ targetKs.authorityRequirements.push({ id, requiredAuthority: sourceId });
775
+ defs.diagram.edges.push({ dmnElementRef: id, waypoints: [] });
776
+ }
777
+ else if (targetBkm) {
778
+ if (sourceIsBkm) {
779
+ targetBkm.knowledgeRequirements.push({ id, requiredKnowledge: sourceId });
780
+ defs.diagram.edges.push({ dmnElementRef: id, waypoints: [] });
781
+ }
782
+ else if (sourceIsKs) {
783
+ targetBkm.authorityRequirements.push({ id, requiredAuthority: sourceId });
784
+ defs.diagram.edges.push({ dmnElementRef: id, waypoints: [] });
785
+ }
786
+ else {
787
+ return;
788
+ }
789
+ }
790
+ else {
791
+ const assocId = `assoc_${uid()}`;
792
+ defs.associations.push({ id: assocId, sourceRef: sourceId, targetRef: targetId });
793
+ defs.diagram.edges.push({ dmnElementRef: assocId, waypoints: [] });
794
+ }
795
+ this._render();
796
+ this._onChange();
797
+ }
798
+ // ── Deletion ───────────────────────────────────────────────────────────────
799
+ _deleteSelected() {
800
+ for (const id of this._selectedIds)
801
+ this._deleteElement(id);
802
+ this._selectedIds.clear();
803
+ this._render();
804
+ this._onChange();
805
+ }
806
+ _deleteElement(id) {
807
+ const defs = this._defs;
808
+ if (defs.diagram) {
809
+ defs.diagram.shapes = defs.diagram.shapes.filter((s) => s.dmnElementRef !== id);
810
+ defs.diagram.edges = defs.diagram.edges.filter((e) => e.dmnElementRef !== id);
811
+ }
812
+ const removeFrom = (arr) => {
813
+ const idx = arr.findIndex((x) => x.id === id);
814
+ if (idx === -1)
815
+ return false;
816
+ arr.splice(idx, 1);
817
+ return true;
818
+ };
819
+ if (removeFrom(defs.decisions)) {
820
+ this._removeEdgesReferencing(id);
821
+ return;
822
+ }
823
+ if (removeFrom(defs.inputData)) {
824
+ this._removeEdgesReferencing(id);
825
+ return;
826
+ }
827
+ if (removeFrom(defs.knowledgeSources)) {
828
+ this._removeEdgesReferencing(id);
829
+ return;
830
+ }
831
+ if (removeFrom(defs.businessKnowledgeModels)) {
832
+ this._removeEdgesReferencing(id);
833
+ return;
834
+ }
835
+ if (removeFrom(defs.textAnnotations)) {
836
+ this._removeEdgesReferencing(id);
837
+ return;
838
+ }
839
+ this._deleteRequirement(id);
840
+ }
841
+ _removeEdgesReferencing(nodeId) {
842
+ const defs = this._defs;
843
+ for (const d of defs.decisions) {
844
+ d.informationRequirements = d.informationRequirements.filter((r) => r.requiredDecision !== nodeId && r.requiredInput !== nodeId);
845
+ d.knowledgeRequirements = d.knowledgeRequirements.filter((r) => r.requiredKnowledge !== nodeId);
846
+ d.authorityRequirements = d.authorityRequirements.filter((r) => r.requiredAuthority !== nodeId &&
847
+ r.requiredDecision !== nodeId &&
848
+ r.requiredInput !== nodeId);
849
+ }
850
+ for (const ks of defs.knowledgeSources) {
851
+ ks.authorityRequirements = ks.authorityRequirements.filter((r) => r.requiredAuthority !== nodeId &&
852
+ r.requiredDecision !== nodeId &&
853
+ r.requiredInput !== nodeId);
854
+ }
855
+ for (const bkm of defs.businessKnowledgeModels) {
856
+ bkm.knowledgeRequirements = bkm.knowledgeRequirements.filter((r) => r.requiredKnowledge !== nodeId);
857
+ bkm.authorityRequirements = bkm.authorityRequirements.filter((r) => r.requiredAuthority !== nodeId &&
858
+ r.requiredDecision !== nodeId &&
859
+ r.requiredInput !== nodeId);
860
+ }
861
+ defs.associations = defs.associations.filter((a) => a.sourceRef !== nodeId && a.targetRef !== nodeId);
862
+ }
863
+ _deleteRequirement(reqId) {
864
+ const defs = this._defs;
865
+ for (const d of defs.decisions) {
866
+ d.informationRequirements = d.informationRequirements.filter((r) => r.id !== reqId);
867
+ d.knowledgeRequirements = d.knowledgeRequirements.filter((r) => r.id !== reqId);
868
+ d.authorityRequirements = d.authorityRequirements.filter((r) => r.id !== reqId);
869
+ }
870
+ for (const ks of defs.knowledgeSources) {
871
+ ks.authorityRequirements = ks.authorityRequirements.filter((r) => r.id !== reqId);
872
+ }
873
+ for (const bkm of defs.businessKnowledgeModels) {
874
+ bkm.knowledgeRequirements = bkm.knowledgeRequirements.filter((r) => r.id !== reqId);
875
+ bkm.authorityRequirements = bkm.authorityRequirements.filter((r) => r.id !== reqId);
876
+ }
877
+ defs.associations = defs.associations.filter((a) => a.id !== reqId);
878
+ if (defs.diagram) {
879
+ defs.diagram.edges = defs.diagram.edges.filter((e) => e.dmnElementRef !== reqId);
880
+ }
881
+ }
882
+ // ── Events ─────────────────────────────────────────────────────────────────
883
+ _bindEvents() {
884
+ const svg = this._svg;
885
+ const onWheel = (e) => {
886
+ e.preventDefault();
887
+ const rect = svg.getBoundingClientRect();
888
+ const mx = e.clientX - rect.left;
889
+ const my = e.clientY - rect.top;
890
+ const factor = e.deltaY > 0 ? 0.9 : 1.1;
891
+ const newScale = Math.min(4, Math.max(0.2, this._scale * factor));
892
+ this._vpX = mx - (mx - this._vpX) * (newScale / this._scale);
893
+ this._vpY = my - (my - this._vpY) * (newScale / this._scale);
894
+ this._scale = newScale;
895
+ this._updateTransform();
896
+ };
897
+ svg.addEventListener("wheel", onWheel, { passive: false });
898
+ this._cleanups.push(() => svg.removeEventListener("wheel", onWheel));
899
+ const onSvgMouseDown = (e) => {
900
+ const t = e.target;
901
+ if (t !== svg && t !== this._vpGroup && t !== this._edgesLayer && t !== this._nodesLayer)
902
+ return;
903
+ if (this._connectMode) {
904
+ // Clicking empty space cancels connect mode but keeps selection
905
+ this._connectMode = false;
906
+ this._connectSourceId = null;
907
+ this._render();
908
+ return;
909
+ }
910
+ this._selectedIds.clear();
911
+ this._render();
912
+ this._panStart = { mx: e.clientX, my: e.clientY, vpX: this._vpX, vpY: this._vpY };
913
+ };
914
+ svg.addEventListener("mousedown", onSvgMouseDown);
915
+ this._cleanups.push(() => svg.removeEventListener("mousedown", onSvgMouseDown));
916
+ const onMouseMove = (e) => {
917
+ if (this._panStart) {
918
+ this._vpX = this._panStart.vpX + (e.clientX - this._panStart.mx);
919
+ this._vpY = this._panStart.vpY + (e.clientY - this._panStart.my);
920
+ this._updateTransform();
921
+ }
922
+ else if (this._dragNode) {
923
+ const rawDx = (e.clientX - this._dragNode.startMx) / this._scale;
924
+ const rawDy = (e.clientY - this._dragNode.startMy) / this._scale;
925
+ const shape = this._defs.diagram?.shapes.find((s) => s.dmnElementRef === this._dragNode?.id);
926
+ if (shape) {
927
+ const candidateX = this._dragNode.startX + rawDx;
928
+ const candidateY = this._dragNode.startY + rawDy;
929
+ const { adjX, adjY, guides } = this._computeSnap(candidateX, candidateY, shape.bounds.width, shape.bounds.height, this._dragNode.id);
930
+ shape.bounds.x = candidateX + adjX;
931
+ shape.bounds.y = candidateY + adjY;
932
+ this._renderGuides(guides);
933
+ const nodeEl = this._nodesLayer.querySelector(`[data-id="${this._dragNode.id}"]`);
934
+ nodeEl?.setAttribute("transform", `translate(${shape.bounds.x},${shape.bounds.y})`);
935
+ this._edgesLayer.innerHTML = "";
936
+ const nodes = this._buildNodes();
937
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
938
+ for (const edge of this._buildEdges()) {
939
+ const src = nodeMap.get(edge.sourceId);
940
+ const tgt = nodeMap.get(edge.targetId);
941
+ if (src && tgt)
942
+ this._edgesLayer.appendChild(this._renderEdge(edge, src, tgt));
943
+ }
944
+ this._updateCtxBarPosition();
945
+ }
946
+ }
947
+ };
948
+ document.addEventListener("mousemove", onMouseMove);
949
+ this._cleanups.push(() => document.removeEventListener("mousemove", onMouseMove));
950
+ const onMouseUp = () => {
951
+ if (this._dragNode) {
952
+ this._clearGuides();
953
+ this._onChange();
954
+ }
955
+ this._panStart = null;
956
+ this._dragNode = null;
957
+ };
958
+ document.addEventListener("mouseup", onMouseUp);
959
+ this._cleanups.push(() => document.removeEventListener("mouseup", onMouseUp));
960
+ const onKeyDown = (e) => {
961
+ if (e.key === "Escape") {
962
+ if (this._connectMode) {
963
+ this._connectMode = false;
964
+ this._connectSourceId = null;
965
+ this._render();
966
+ }
967
+ return;
968
+ }
969
+ if (e.key !== "Delete" && e.key !== "Backspace")
970
+ return;
971
+ const active = document.activeElement;
972
+ if (active instanceof HTMLInputElement ||
973
+ active instanceof HTMLTextAreaElement ||
974
+ active instanceof HTMLSelectElement)
975
+ return;
976
+ if (this._selectedIds.size > 0)
977
+ this._deleteSelected();
978
+ };
979
+ document.addEventListener("keydown", onKeyDown);
980
+ this._cleanups.push(() => document.removeEventListener("keydown", onKeyDown));
981
+ }
982
+ _onNodeMouseDown(e, n) {
983
+ e.stopPropagation();
984
+ if (this._connectMode) {
985
+ if (!this._connectSourceId) {
986
+ this._connectSourceId = n.id;
987
+ this._render();
988
+ }
989
+ else if (this._connectSourceId !== n.id) {
990
+ const src = this._connectSourceId;
991
+ this._connectSourceId = null;
992
+ this._connectMode = false;
993
+ this._connectNodes(src, n.id);
994
+ }
995
+ return;
996
+ }
997
+ this._selectedIds.clear();
998
+ this._selectedIds.add(n.id);
999
+ const shape = this._defs.diagram?.shapes.find((s) => s.dmnElementRef === n.id);
1000
+ if (shape) {
1001
+ this._dragNode = {
1002
+ id: n.id,
1003
+ startMx: e.clientX,
1004
+ startMy: e.clientY,
1005
+ startX: shape.bounds.x,
1006
+ startY: shape.bounds.y,
1007
+ };
1008
+ }
1009
+ this._render();
1010
+ }
1011
+ _onNodeDblClick(e, n) {
1012
+ e.stopPropagation();
1013
+ if (n.type === "decision") {
1014
+ this._onDecisionOpen(n.id);
1015
+ return;
1016
+ }
1017
+ this._startInlineEdit(n);
1018
+ }
1019
+ _startInlineEdit(n) {
1020
+ const shape = this._defs.diagram?.shapes.find((s) => s.dmnElementRef === n.id);
1021
+ if (!shape)
1022
+ return;
1023
+ this._vpGroup.querySelector(".drd-inline-edit-fo")?.remove();
1024
+ const fo = svgEl("foreignObject");
1025
+ cls(fo, "drd-inline-edit-fo");
1026
+ setAttrs(fo, {
1027
+ x: shape.bounds.x,
1028
+ y: shape.bounds.y,
1029
+ width: shape.bounds.width,
1030
+ height: shape.bounds.height,
1031
+ });
1032
+ const inp = document.createElement("input");
1033
+ inp.type = "text";
1034
+ inp.className = "drd-inline-edit";
1035
+ inp.value = n.label;
1036
+ inp.style.cssText = `width:${shape.bounds.width}px;height:${shape.bounds.height}px;`;
1037
+ fo.appendChild(inp);
1038
+ this._vpGroup.appendChild(fo);
1039
+ inp.focus();
1040
+ inp.select();
1041
+ const commit = () => {
1042
+ const val = inp.value.trim() || n.label;
1043
+ this._setNodeLabel(n.id, val);
1044
+ fo.remove();
1045
+ this._render();
1046
+ this._onChange();
1047
+ };
1048
+ inp.addEventListener("blur", commit);
1049
+ inp.addEventListener("keydown", (ev) => {
1050
+ if (ev.key === "Enter") {
1051
+ ev.preventDefault();
1052
+ inp.blur();
1053
+ }
1054
+ else if (ev.key === "Escape") {
1055
+ fo.remove();
1056
+ this._render();
1057
+ }
1058
+ });
1059
+ }
1060
+ _setNodeLabel(id, label) {
1061
+ const defs = this._defs;
1062
+ const d = defs.decisions.find((x) => x.id === id);
1063
+ if (d) {
1064
+ d.name = label;
1065
+ return;
1066
+ }
1067
+ const inp = defs.inputData.find((x) => x.id === id);
1068
+ if (inp) {
1069
+ inp.name = label;
1070
+ return;
1071
+ }
1072
+ const ks = defs.knowledgeSources.find((x) => x.id === id);
1073
+ if (ks) {
1074
+ ks.name = label;
1075
+ return;
1076
+ }
1077
+ const bkm = defs.businessKnowledgeModels.find((x) => x.id === id);
1078
+ if (bkm) {
1079
+ bkm.name = label;
1080
+ return;
1081
+ }
1082
+ const ann = defs.textAnnotations.find((x) => x.id === id);
1083
+ if (ann)
1084
+ ann.text = label;
1085
+ }
1086
+ }
1087
+ //# sourceMappingURL=drd-canvas.js.map