@haikal-fikri/archimate 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1368 @@
1
+ // src/colors.ts
2
+ var LAYER_COLORS = {
3
+ Strategy: "#F5DEAA",
4
+ Resource: "#F5DEAA",
5
+ Capability: "#F5DEAA",
6
+ CourseOfAction: "#F5DEAA",
7
+ ValueStream: "#F5DEAA",
8
+ Business: "#FFFFB5",
9
+ BusinessActor: "#FFFFB5",
10
+ BusinessRole: "#FFFFB5",
11
+ BusinessCollaboration: "#FFFFB5",
12
+ BusinessInterface: "#FFFFB5",
13
+ BusinessProcess: "#FFFFB5",
14
+ BusinessFunction: "#FFFFB5",
15
+ BusinessInteraction: "#FFFFB5",
16
+ BusinessEvent: "#FFFFB5",
17
+ BusinessService: "#FFFFB5",
18
+ BusinessObject: "#FFFFB5",
19
+ Contract: "#FFFFB5",
20
+ Representation: "#FFFFB5",
21
+ Product: "#FFFFB5",
22
+ Application: "#B5FFFF",
23
+ ApplicationComponent: "#B5FFFF",
24
+ ApplicationCollaboration: "#B5FFFF",
25
+ ApplicationInterface: "#B5FFFF",
26
+ ApplicationFunction: "#B5FFFF",
27
+ ApplicationInteraction: "#B5FFFF",
28
+ ApplicationProcess: "#B5FFFF",
29
+ ApplicationEvent: "#B5FFFF",
30
+ ApplicationService: "#B5FFFF",
31
+ DataObject: "#B5FFFF",
32
+ Technology: "#C9E7B7",
33
+ TechnologyCollaboration: "#C9E7B7",
34
+ TechnologyInterface: "#C9E7B7",
35
+ TechnologyFunction: "#C9E7B7",
36
+ TechnologyProcess: "#C9E7B7",
37
+ TechnologyInteraction: "#C9E7B7",
38
+ TechnologyEvent: "#C9E7B7",
39
+ TechnologyService: "#C9E7B7",
40
+ Node: "#C9E7B7",
41
+ Device: "#C9E7B7",
42
+ SystemSoftware: "#C9E7B7",
43
+ Path: "#C9E7B7",
44
+ CommunicationNetwork: "#C9E7B7",
45
+ Artifact: "#C9E7B7",
46
+ Equipment: "#C9E7B7",
47
+ Facility: "#C9E7B7",
48
+ DistributionNetwork: "#C9E7B7",
49
+ Material: "#C9E7B7",
50
+ Stakeholder: "#CCCCFF",
51
+ Driver: "#CCCCFF",
52
+ Assessment: "#CCCCFF",
53
+ Goal: "#CCCCFF",
54
+ Outcome: "#CCCCFF",
55
+ Principle: "#CCCCFF",
56
+ Requirement: "#CCCCFF",
57
+ Constraint: "#CCCCFF",
58
+ Meaning: "#CCCCFF",
59
+ Value: "#CCCCFF",
60
+ WorkPackage: "#E0FFE0",
61
+ Deliverable: "#E0FFE0",
62
+ Plateau: "#E0FFE0",
63
+ Gap: "#E0FFE0",
64
+ ImplementationEvent: "#E0FFE0",
65
+ AndJunction: "#000000",
66
+ OrJunction: "#000000",
67
+ Junction: "#000000"
68
+ };
69
+ var DEFAULT_FILL = "#FFFFFF";
70
+ var DEFAULT_STROKE = "#1A1A1A";
71
+ var DEFAULT_TEXT = "#1A1A1A";
72
+ function colorForType(elementType) {
73
+ if (!elementType) return DEFAULT_FILL;
74
+ if (LAYER_COLORS[elementType]) return LAYER_COLORS[elementType];
75
+ const prefixes = ["Strategy", "Business", "Application", "Technology", "Motivation", "Implementation"];
76
+ for (const p of prefixes) {
77
+ if (elementType.startsWith(p)) {
78
+ return LAYER_COLORS[p] ?? DEFAULT_FILL;
79
+ }
80
+ }
81
+ if (elementType.endsWith("Junction")) return LAYER_COLORS.Junction;
82
+ return DEFAULT_FILL;
83
+ }
84
+
85
+ // src/parser.ts
86
+ var ACCESS_TYPES = /* @__PURE__ */ new Set(["Access", "Read", "Write", "ReadWrite"]);
87
+ function isJunctionType(t) {
88
+ if (!t) return false;
89
+ return t === "Junction" || t === "AndJunction" || t === "OrJunction";
90
+ }
91
+ var OPEN_EXCHANGE_NS_V3 = "http://www.opengroup.org/xsd/archimate/3.0/";
92
+ var OPEN_EXCHANGE_NS_V2 = "http://www.opengroup.org/xsd/archimate";
93
+ var OPEN_EXCHANGE_NAMESPACES = /* @__PURE__ */ new Set([
94
+ OPEN_EXCHANGE_NS_V3,
95
+ OPEN_EXCHANGE_NS_V2
96
+ ]);
97
+ var NATIVE_ARCHI_NS = "http://www.archimatetool.com/archimate";
98
+ var XSI_NS = "http://www.w3.org/2001/XMLSchema-instance";
99
+ var RELATIVE_COORDINATES = false;
100
+ var ArchimateParseError = class extends Error {
101
+ kind;
102
+ constructor(message, kind) {
103
+ super(message);
104
+ this.name = "ArchimateParseError";
105
+ this.kind = kind;
106
+ }
107
+ };
108
+ function childrenOf(el, name) {
109
+ return Array.from(el.children).filter((c) => c.localName === name);
110
+ }
111
+ function firstChild(el, name) {
112
+ for (const c of Array.from(el.children)) if (c.localName === name) return c;
113
+ return null;
114
+ }
115
+ function xsiType(el) {
116
+ return el.getAttributeNS(XSI_NS, "type") ?? el.getAttribute("xsi:type") ?? void 0;
117
+ }
118
+ function textOf(el) {
119
+ return (el?.textContent ?? "").trim();
120
+ }
121
+ function nameOrLabel(el) {
122
+ return textOf(firstChild(el, "name")) || textOf(firstChild(el, "label"));
123
+ }
124
+ function normalizeRelationshipType(raw) {
125
+ if (!raw) return void 0;
126
+ const stripped = raw.endsWith("Relationship") ? raw.slice(0, -"Relationship".length) : raw;
127
+ if (stripped === "Realisation") return "Realization";
128
+ if (stripped === "Specialisation") return "Specialization";
129
+ return stripped;
130
+ }
131
+ var ELEMENT_TYPE_RENAMES = {
132
+ InfrastructureService: "TechnologyService",
133
+ InfrastructureFunction: "TechnologyFunction",
134
+ InfrastructureInterface: "TechnologyInterface",
135
+ Network: "CommunicationNetwork"
136
+ };
137
+ function normalizeElementType(raw) {
138
+ if (!raw) return void 0;
139
+ return ELEMENT_TYPE_RENAMES[raw] ?? raw;
140
+ }
141
+ function attr(el, ...names) {
142
+ for (const n of names) {
143
+ const v = el.getAttribute(n);
144
+ if (v !== null) return v;
145
+ }
146
+ return null;
147
+ }
148
+ function rgbaFromColor(el) {
149
+ if (!el) return void 0;
150
+ const r = el.getAttribute("r");
151
+ const g = el.getAttribute("g");
152
+ const b = el.getAttribute("b");
153
+ const a = el.getAttribute("a");
154
+ if (r === null || g === null || b === null) return void 0;
155
+ const alpha = a === null ? 1 : Math.max(0, Math.min(1, Number(a) / 100));
156
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
157
+ }
158
+ function num(el, attr2, fallback = 0) {
159
+ const v = el.getAttribute(attr2);
160
+ if (v === null) return fallback;
161
+ const n = Number(v);
162
+ return Number.isFinite(n) ? n : fallback;
163
+ }
164
+ function parseArchimate(xml, viewId) {
165
+ if (typeof xml !== "string" || xml.trim().length === 0) {
166
+ throw new ArchimateParseError("No XML provided.", "invalid-xml");
167
+ }
168
+ let doc;
169
+ try {
170
+ doc = new DOMParser().parseFromString(xml, "application/xml");
171
+ } catch (e) {
172
+ throw new ArchimateParseError(`Could not parse XML: ${e.message}`, "invalid-xml");
173
+ }
174
+ const errEl = doc.getElementsByTagName("parsererror")[0];
175
+ if (errEl) {
176
+ const msg = errEl.textContent?.split("\n").find((l) => l.trim().length > 0) ?? "malformed XML";
177
+ throw new ArchimateParseError(`Invalid XML: ${msg.trim()}`, "invalid-xml");
178
+ }
179
+ const root = doc.documentElement;
180
+ if (!root) {
181
+ throw new ArchimateParseError("XML document has no root element.", "invalid-xml");
182
+ }
183
+ if (root.namespaceURI === NATIVE_ARCHI_NS) {
184
+ throw new ArchimateParseError(
185
+ "This looks like Archi's native .archimate file. In Archi: File \u2192 Export \u2192 Open Exchange XML, then paste the exported XML here instead.",
186
+ "wrong-format"
187
+ );
188
+ }
189
+ if (!root.namespaceURI || !OPEN_EXCHANGE_NAMESPACES.has(root.namespaceURI)) {
190
+ throw new ArchimateParseError(
191
+ `Expected ArchiMate Open Exchange XML (namespace ${OPEN_EXCHANGE_NS_V3} or ${OPEN_EXCHANGE_NS_V2}). Got ${root.namespaceURI ?? "no namespace"}.`,
192
+ "wrong-format"
193
+ );
194
+ }
195
+ const elementsEl = firstChild(root, "elements");
196
+ const elementMap = /* @__PURE__ */ new Map();
197
+ if (elementsEl) {
198
+ for (const el of childrenOf(elementsEl, "element")) {
199
+ const id = el.getAttribute("identifier");
200
+ if (!id) continue;
201
+ elementMap.set(id, {
202
+ name: nameOrLabel(el) || id,
203
+ type: normalizeElementType(xsiType(el))
204
+ });
205
+ }
206
+ }
207
+ const relationshipsEl = firstChild(root, "relationships");
208
+ const relationshipMap = /* @__PURE__ */ new Map();
209
+ if (relationshipsEl) {
210
+ for (const r of childrenOf(relationshipsEl, "relationship")) {
211
+ const id = r.getAttribute("identifier");
212
+ const source = r.getAttribute("source");
213
+ const target = r.getAttribute("target");
214
+ if (!id || !source || !target) continue;
215
+ const rawAccess = r.getAttribute("accessType");
216
+ const accessType = rawAccess && ACCESS_TYPES.has(rawAccess) ? rawAccess : void 0;
217
+ const isDirectedAttr = r.getAttribute("isDirected");
218
+ const isDirected = isDirectedAttr === "true" ? true : isDirectedAttr === "false" ? false : void 0;
219
+ relationshipMap.set(id, {
220
+ source,
221
+ target,
222
+ type: normalizeRelationshipType(xsiType(r)),
223
+ accessType,
224
+ isDirected,
225
+ name: nameOrLabel(r) || void 0
226
+ });
227
+ }
228
+ }
229
+ const viewsEl = firstChild(root, "views");
230
+ if (!viewsEl) throw new ArchimateParseError("Model has no <views> section.", "no-views");
231
+ const viewParent = firstChild(viewsEl, "diagrams") ?? viewsEl;
232
+ const allViews = childrenOf(viewParent, "view");
233
+ if (allViews.length === 0) throw new ArchimateParseError("Model contains no views.", "no-views");
234
+ let viewEl = null;
235
+ if (viewId) {
236
+ viewEl = allViews.find((v) => v.getAttribute("identifier") === viewId) ?? null;
237
+ if (!viewEl) {
238
+ throw new ArchimateParseError(
239
+ `No view with identifier "${viewId}" found in this model.`,
240
+ "view-not-found"
241
+ );
242
+ }
243
+ } else {
244
+ viewEl = allViews[0];
245
+ }
246
+ const viewName = nameOrLabel(viewEl) || viewEl.getAttribute("identifier") || "Untitled view";
247
+ const nodes = [];
248
+ function walkNode(nodeEl, offsetX, offsetY) {
249
+ const id = nodeEl.getAttribute("identifier");
250
+ if (!id) return;
251
+ const lx = num(nodeEl, "x");
252
+ const ly = num(nodeEl, "y");
253
+ const w = num(nodeEl, "w");
254
+ const h = num(nodeEl, "h");
255
+ const x = RELATIVE_COORDINATES ? lx + offsetX : lx;
256
+ const y = RELATIVE_COORDINATES ? ly + offsetY : ly;
257
+ const elementRef = attr(nodeEl, "elementRef", "elementref");
258
+ const elementInfo = elementRef ? elementMap.get(elementRef) : void 0;
259
+ const nodeType = xsiType(nodeEl);
260
+ const styleEl = firstChild(nodeEl, "style");
261
+ const lineEl = styleEl ? firstChild(styleEl, "lineColor") : null;
262
+ const fontEl = styleEl ? firstChild(styleEl, "font") : null;
263
+ const fontColorEl = fontEl ? firstChild(fontEl, "color") : null;
264
+ const fontSizeAttr = fontEl?.getAttribute("size");
265
+ const fontSize = fontSizeAttr ? Number(fontSizeAttr) : void 0;
266
+ nodes.push({
267
+ id,
268
+ elementId: elementRef ?? void 0,
269
+ elementType: elementInfo?.type,
270
+ nodeType,
271
+ isJunction: isJunctionType(elementInfo?.type),
272
+ label: elementInfo?.name ?? nameOrLabel(nodeEl),
273
+ x,
274
+ y,
275
+ w,
276
+ h,
277
+ fill: colorForType(elementInfo?.type),
278
+ stroke: rgbaFromColor(lineEl) ?? DEFAULT_STROKE,
279
+ textColor: rgbaFromColor(fontColorEl) ?? DEFAULT_TEXT,
280
+ fontFamily: fontEl?.getAttribute("name") ?? void 0,
281
+ fontSize: fontSize && Number.isFinite(fontSize) ? fontSize : void 0
282
+ });
283
+ for (const childNode of childrenOf(nodeEl, "node")) {
284
+ walkNode(childNode, x, y);
285
+ }
286
+ }
287
+ for (const n of childrenOf(viewEl, "node")) walkNode(n, 0, 0);
288
+ const nodeMap = /* @__PURE__ */ new Map();
289
+ for (const n of nodes) nodeMap.set(n.id, n);
290
+ const connections = [];
291
+ for (const c of childrenOf(viewEl, "connection")) {
292
+ const id = c.getAttribute("identifier");
293
+ const source = c.getAttribute("source");
294
+ const target = c.getAttribute("target");
295
+ if (!id || !source || !target) continue;
296
+ if (!nodeMap.has(source) || !nodeMap.has(target)) continue;
297
+ const relRef = attr(c, "relationshipRef", "relationshipref");
298
+ const relInfo = relRef ? relationshipMap.get(relRef) : void 0;
299
+ const bendpoints = [];
300
+ for (const bp of childrenOf(c, "bendpoint")) {
301
+ bendpoints.push({ x: num(bp, "x"), y: num(bp, "y") });
302
+ }
303
+ const styleEl = firstChild(c, "style");
304
+ const lineEl = styleEl ? firstChild(styleEl, "lineColor") : null;
305
+ connections.push({
306
+ id,
307
+ sourceId: source,
308
+ targetId: target,
309
+ relationshipType: relInfo?.type,
310
+ accessType: relInfo?.accessType,
311
+ isDirected: relInfo?.isDirected,
312
+ name: relInfo?.name,
313
+ bendpoints,
314
+ stroke: rgbaFromColor(lineEl)
315
+ });
316
+ }
317
+ let minX = Infinity;
318
+ let minY = Infinity;
319
+ let maxX = -Infinity;
320
+ let maxY = -Infinity;
321
+ for (const n of nodes) {
322
+ if (n.x < minX) minX = n.x;
323
+ if (n.y < minY) minY = n.y;
324
+ if (n.x + n.w > maxX) maxX = n.x + n.w;
325
+ if (n.y + n.h > maxY) maxY = n.y + n.h;
326
+ }
327
+ if (!Number.isFinite(minX)) {
328
+ minX = 0;
329
+ minY = 0;
330
+ maxX = 100;
331
+ maxY = 100;
332
+ }
333
+ const pad = 40;
334
+ return {
335
+ viewId: viewEl.getAttribute("identifier") ?? "",
336
+ viewName,
337
+ viewBox: {
338
+ x: minX - pad,
339
+ y: minY - pad,
340
+ width: maxX - minX + pad * 2,
341
+ height: maxY - minY + pad * 2
342
+ },
343
+ nodes,
344
+ connections
345
+ };
346
+ }
347
+
348
+ // src/renderer.tsx
349
+ import {
350
+ forwardRef,
351
+ useEffect,
352
+ useImperativeHandle,
353
+ useMemo,
354
+ useRef
355
+ } from "react";
356
+
357
+ // src/icons.ts
358
+ var ICONS = {
359
+ // ─── Active structure ──────────────────────────────────────────────────────
360
+ // Actor / Stakeholder — stick figure with head, body, arms, legs
361
+ actor: {
362
+ outline: [
363
+ "M 50 22 m -10 0 a 10 10 0 1 0 20 0 a 10 10 0 1 0 -20 0",
364
+ "M 50 32 L 50 60",
365
+ "M 22 46 L 78 46",
366
+ "M 50 60 L 28 88",
367
+ "M 50 60 L 72 88"
368
+ ]
369
+ },
370
+ // Role — horizontal pill with a small filled dot on the right
371
+ role: {
372
+ outline: ["M 22 32 L 78 32 a 18 18 0 0 1 0 36 L 22 68 a 18 18 0 0 1 0 -36 Z"],
373
+ filled: ["M 70 50 m -7 0 a 7 7 0 1 0 14 0 a 7 7 0 1 0 -14 0"]
374
+ },
375
+ // Collaboration — two full overlapping circles (Venn-style, symmetric)
376
+ collaboration: {
377
+ outline: [
378
+ "M 36 50 m -22 0 a 22 22 0 1 0 44 0 a 22 22 0 1 0 -44 0",
379
+ "M 64 50 m -22 0 a 22 22 0 1 0 44 0 a 22 22 0 1 0 -44 0"
380
+ ]
381
+ },
382
+ // Interaction — two semicircles (D-shapes) facing each other with a gap
383
+ interaction: {
384
+ outline: [
385
+ "M 42 22 A 28 28 0 0 0 42 78 Z",
386
+ "M 58 22 A 28 28 0 0 1 58 78 Z"
387
+ ]
388
+ },
389
+ // Interface — lollipop (line on left, filled circle on right)
390
+ interface: {
391
+ outline: [
392
+ "M 64 50 m -16 0 a 16 16 0 1 0 32 0 a 16 16 0 1 0 -32 0",
393
+ "M 12 50 L 48 50"
394
+ ]
395
+ },
396
+ // Application Component — main rectangle + two connector tabs on the left
397
+ component: {
398
+ outline: [
399
+ "M 30 22 L 88 22 L 88 78 L 30 78 Z",
400
+ "M 14 32 L 38 32 L 38 46 L 14 46 Z",
401
+ "M 14 54 L 38 54 L 38 68 L 14 68 Z"
402
+ ]
403
+ },
404
+ // ─── Behavior ──────────────────────────────────────────────────────────────
405
+ // Process — right-pointing arrow / chevron
406
+ process: {
407
+ outline: ["M 12 35 L 56 35 L 56 22 L 88 50 L 56 78 L 56 65 L 12 65 Z"]
408
+ },
409
+ // Function — concentric chevrons matching the ArchiMate reference notation.
410
+ // Outer V (apex up) and inner V (apex up, lower) sharing the same bottom
411
+ // axis. Both outer-bottom corners and both inner-bottom corners sit on the
412
+ // bottom edge; the inner V's apex sits above, forming the top of the stripe.
413
+ // Vertices (clockwise): outer-bottom-left, outer-apex, outer-bottom-right,
414
+ // inner-bottom-right, inner-apex, inner-bottom-left.
415
+ function: {
416
+ outline: ["M 10 82 L 50 18 L 90 82 L 72 82 L 50 42 L 28 82 Z"]
417
+ },
418
+ // Event — D-shape with chevron notch on the left
419
+ event: {
420
+ outline: ["M 70 22 a 28 28 0 0 1 0 56 L 28 78 L 48 50 L 28 22 Z"]
421
+ },
422
+ // Service — pill / rounded rectangle
423
+ service: {
424
+ outline: ["M 30 32 L 70 32 a 18 18 0 0 1 0 36 L 30 68 a 18 18 0 0 1 0 -36 Z"]
425
+ },
426
+ // ─── Passive structure ─────────────────────────────────────────────────────
427
+ // Object / Data Object — rectangle with filled header bar
428
+ object: {
429
+ outline: ["M 15 22 L 85 22 L 85 78 L 15 78 Z", "M 15 36 L 85 36"],
430
+ filled: ["M 15 22 L 85 22 L 85 36 L 15 36 Z"]
431
+ },
432
+ // Contract / Material — rectangle with several horizontal text lines
433
+ contract: {
434
+ outline: [
435
+ "M 18 18 L 82 18 L 82 82 L 18 82 Z",
436
+ "M 28 32 L 72 32",
437
+ "M 28 44 L 72 44",
438
+ "M 28 56 L 72 56",
439
+ "M 28 68 L 60 68"
440
+ ]
441
+ },
442
+ // Representation — rectangle with wavy bottom edge
443
+ representation: {
444
+ outline: ["M 18 18 L 82 18 L 82 70 Q 70 88 50 76 Q 30 64 18 82 Z"]
445
+ },
446
+ // Product — rectangle with a small tab notched on the top-left
447
+ product: {
448
+ outline: ["M 18 28 L 38 28 L 42 18 L 82 18 L 82 82 L 18 82 Z"]
449
+ },
450
+ // Artifact — page with folded upper-right corner
451
+ artifact: {
452
+ outline: ["M 22 14 L 70 14 L 86 30 L 86 86 L 22 86 Z", "M 70 14 L 70 30 L 86 30"]
453
+ },
454
+ // ─── Technology ────────────────────────────────────────────────────────────
455
+ // Node — 3D wireframe box
456
+ node: {
457
+ outline: [
458
+ "M 18 38 L 70 38 L 70 86 L 18 86 Z",
459
+ "M 18 38 L 32 22 L 84 22 L 70 38",
460
+ "M 70 38 L 84 22 L 84 70 L 70 86"
461
+ ]
462
+ },
463
+ // Device — computer monitor with stand
464
+ device: {
465
+ outline: [
466
+ "M 12 22 L 88 22 L 88 64 L 12 64 Z",
467
+ "M 28 78 L 72 78",
468
+ "M 50 64 L 50 78"
469
+ ]
470
+ },
471
+ // System Software — two asymmetrically overlapping circles (different sizes,
472
+ // off-centre overlap — distinct from the symmetric Venn used for Collaboration)
473
+ systemsoftware: {
474
+ outline: [
475
+ "M 38 58 m -28 0 a 28 28 0 1 0 56 0 a 28 28 0 1 0 -56 0",
476
+ "M 72 32 m -16 0 a 16 16 0 1 0 32 0 a 16 16 0 1 0 -32 0"
477
+ ]
478
+ },
479
+ // Communication Network — parallelogram with filled circle nodes at 4 corners
480
+ network: {
481
+ outline: ["M 28 22 L 88 22 L 72 78 L 12 78 Z"],
482
+ filled: [
483
+ "M 28 22 m -5 0 a 5 5 0 1 0 10 0 a 5 5 0 1 0 -10 0",
484
+ "M 88 22 m -5 0 a 5 5 0 1 0 10 0 a 5 5 0 1 0 -10 0",
485
+ "M 72 78 m -5 0 a 5 5 0 1 0 10 0 a 5 5 0 1 0 -10 0",
486
+ "M 12 78 m -5 0 a 5 5 0 1 0 10 0 a 5 5 0 1 0 -10 0"
487
+ ]
488
+ },
489
+ // Path — bidirectional arrows with a centre dot
490
+ path: {
491
+ outline: [
492
+ "M 12 50 L 38 50",
493
+ "M 22 40 L 12 50 L 22 60",
494
+ "M 88 50 L 62 50",
495
+ "M 78 40 L 88 50 L 78 60"
496
+ ],
497
+ filled: ["M 50 50 m -4 0 a 4 4 0 1 0 8 0 a 4 4 0 1 0 -8 0"]
498
+ },
499
+ // Equipment — two cogs (large + small) overlapping. Each cog: circle +
500
+ // radial teeth at cardinal & intercardinal directions + filled hub.
501
+ equipment: {
502
+ outline: [
503
+ // Big cog at (38, 60), body r=18, teeth out to r=22
504
+ "M 38 60 m -18 0 a 18 18 0 1 0 36 0 a 18 18 0 1 0 -36 0",
505
+ "M 38 38 L 38 42",
506
+ "M 38 78 L 38 82",
507
+ "M 16 60 L 20 60",
508
+ "M 56 60 L 60 60",
509
+ "M 23 45 L 26 48",
510
+ "M 50 72 L 53 75",
511
+ "M 23 75 L 26 72",
512
+ "M 50 48 L 53 45",
513
+ // Small cog at (74, 32), body r=10, teeth out to r=13
514
+ "M 74 32 m -10 0 a 10 10 0 1 0 20 0 a 10 10 0 1 0 -20 0",
515
+ "M 74 19 L 74 22",
516
+ "M 74 42 L 74 45",
517
+ "M 61 32 L 64 32",
518
+ "M 84 32 L 87 32"
519
+ ],
520
+ filled: [
521
+ "M 38 60 m -4 0 a 4 4 0 1 0 8 0 a 4 4 0 1 0 -8 0",
522
+ "M 74 32 m -2 0 a 2 2 0 1 0 4 0 a 2 2 0 1 0 -4 0"
523
+ ]
524
+ },
525
+ // Facility — factory with sawtooth roof (3 triangular peaks)
526
+ facility: {
527
+ outline: [
528
+ "M 14 86 L 14 38 L 38 14 L 38 38 L 62 14 L 62 38 L 86 14 L 86 38 L 86 86 Z"
529
+ ]
530
+ },
531
+ // Distribution Network — two parallel horizontal lines with arrowheads at
532
+ // both ends (a "double-arrow" mathematical symbol ⇔)
533
+ distributionnetwork: {
534
+ outline: [
535
+ "M 24 45 L 76 45",
536
+ "M 24 55 L 76 55",
537
+ "M 24 45 L 12 50 L 24 55",
538
+ "M 76 45 L 88 50 L 76 55"
539
+ ]
540
+ },
541
+ // Material — hexagon with internal horizontal lines
542
+ material: {
543
+ outline: [
544
+ "M 50 14 L 84 32 L 84 68 L 50 86 L 16 68 L 16 32 Z",
545
+ "M 24 44 L 76 44",
546
+ "M 24 56 L 76 56"
547
+ ]
548
+ },
549
+ // ─── Motivation ────────────────────────────────────────────────────────────
550
+ // Driver — steering wheel: circle with 4-spoke crosshair + center hub
551
+ driver: {
552
+ outline: [
553
+ "M 50 50 m -34 0 a 34 34 0 1 0 68 0 a 34 34 0 1 0 -68 0",
554
+ "M 50 16 L 50 84",
555
+ "M 16 50 L 84 50",
556
+ "M 26 26 L 74 74",
557
+ "M 74 26 L 26 74"
558
+ ],
559
+ filled: ["M 50 50 m -7 0 a 7 7 0 1 0 14 0 a 7 7 0 1 0 -14 0"]
560
+ },
561
+ // Assessment — magnifying glass (lens + handle)
562
+ assessment: {
563
+ outline: [
564
+ "M 38 38 m -22 0 a 22 22 0 1 0 44 0 a 22 22 0 1 0 -44 0",
565
+ "M 56 56 L 86 86"
566
+ ]
567
+ },
568
+ // Goal — concentric circles (bullseye / target)
569
+ goal: {
570
+ outline: [
571
+ "M 50 50 m -36 0 a 36 36 0 1 0 72 0 a 36 36 0 1 0 -72 0",
572
+ "M 50 50 m -22 0 a 22 22 0 1 0 44 0 a 22 22 0 1 0 -44 0"
573
+ ],
574
+ filled: ["M 50 50 m -8 0 a 8 8 0 1 0 16 0 a 8 8 0 1 0 -16 0"]
575
+ },
576
+ // Outcome — bullseye with arrow stuck through it
577
+ outcome: {
578
+ outline: [
579
+ "M 50 50 m -28 0 a 28 28 0 1 0 56 0 a 28 28 0 1 0 -56 0",
580
+ "M 50 50 m -10 0 a 10 10 0 1 0 20 0 a 10 10 0 1 0 -20 0",
581
+ "M 78 22 L 50 50",
582
+ "M 66 22 L 78 22 L 78 34"
583
+ ]
584
+ },
585
+ // Principle — tall rectangle with exclamation mark inside (a "tablet")
586
+ principle: {
587
+ outline: ["M 35 14 L 65 14 L 65 86 L 35 86 Z", "M 50 28 L 50 60"],
588
+ filled: ["M 50 70 m -3 0 a 3 3 0 1 0 6 0 a 3 3 0 1 0 -6 0"]
589
+ },
590
+ // Requirement — parallelogram
591
+ requirement: {
592
+ outline: ["M 30 22 L 88 22 L 70 78 L 12 78 Z"]
593
+ },
594
+ // Constraint — parallelogram with horizontal line through middle
595
+ constraint: {
596
+ outline: ["M 30 22 L 88 22 L 70 78 L 12 78 Z", "M 21 50 L 79 50"]
597
+ },
598
+ // Meaning — speech / cloud bubble with tail
599
+ meaning: {
600
+ outline: [
601
+ "M 30 28 Q 18 28 18 42 Q 18 56 30 58 L 25 78 L 42 60 Q 70 64 78 50 Q 86 32 64 26 Q 50 14 30 28 Z"
602
+ ]
603
+ },
604
+ // Value — horizontal ellipse
605
+ value: {
606
+ outline: ["M 50 50 m -32 0 a 32 18 0 1 0 64 0 a 32 18 0 1 0 -64 0"]
607
+ },
608
+ // Stakeholder — pill with filled dot (same family as Role)
609
+ stakeholder: {
610
+ outline: ["M 22 32 L 78 32 a 18 18 0 0 1 0 36 L 22 68 a 18 18 0 0 1 0 -36 Z"],
611
+ filled: ["M 70 50 m -7 0 a 7 7 0 1 0 14 0 a 7 7 0 1 0 -14 0"]
612
+ },
613
+ // ─── Strategy ──────────────────────────────────────────────────────────────
614
+ // Resource — rectangle with three vertical bars (stack of cards)
615
+ resource: {
616
+ outline: [
617
+ "M 18 30 L 82 30 L 82 70 L 18 70 Z",
618
+ "M 36 38 L 36 62",
619
+ "M 50 38 L 50 62",
620
+ "M 64 38 L 64 62"
621
+ ]
622
+ },
623
+ // Capability — stepped staircase (three blocks ascending)
624
+ capability: {
625
+ outline: [
626
+ "M 14 78 L 38 78 L 38 60 L 62 60 L 62 42 L 86 42 L 86 24 L 70 24",
627
+ "M 70 24 L 70 42 L 46 42 L 46 60 L 22 60 L 22 78"
628
+ ]
629
+ },
630
+ // Course of Action — right-pointing arrow (similar to process)
631
+ courseofaction: {
632
+ outline: ["M 12 35 L 56 35 L 56 22 L 88 50 L 56 78 L 56 65 L 12 65 Z"]
633
+ },
634
+ // Value Stream — chevron with concave back
635
+ valuestream: {
636
+ outline: ["M 12 28 L 65 28 L 88 50 L 65 72 L 12 72 L 32 50 Z"]
637
+ },
638
+ // ─── Implementation & Migration ────────────────────────────────────────────
639
+ // Work Package — small rectangle (placeholder; Archi uses a banded shape)
640
+ workpackage: {
641
+ outline: ["M 18 30 L 82 30 L 82 70 L 18 70 Z", "M 18 50 L 82 50"]
642
+ },
643
+ // Deliverable — rectangle with curled / wavy bottom edge
644
+ deliverable: {
645
+ outline: ["M 18 18 L 82 18 L 82 75 Q 70 88 50 78 Q 30 68 18 82 Z"]
646
+ },
647
+ // Plateau — stack of three horizontal bars
648
+ plateau: {
649
+ outline: [
650
+ "M 18 22 L 82 22 L 82 32 L 18 32 Z",
651
+ "M 18 44 L 82 44 L 82 54 L 18 54 Z",
652
+ "M 18 66 L 82 66 L 82 76 L 18 76 Z"
653
+ ]
654
+ },
655
+ // Gap — circle with a horizontal line through the middle
656
+ gap: {
657
+ outline: [
658
+ "M 50 50 m -32 0 a 32 32 0 1 0 64 0 a 32 32 0 1 0 -64 0",
659
+ "M 14 50 L 86 50"
660
+ ]
661
+ }
662
+ };
663
+ function iconNameForType(elementType) {
664
+ if (!elementType) return null;
665
+ const t = elementType;
666
+ if (t === "BusinessActor") return "actor";
667
+ if (t === "BusinessRole") return "role";
668
+ if (t === "BusinessCollaboration" || t === "ApplicationCollaboration" || t === "TechnologyCollaboration")
669
+ return "collaboration";
670
+ if (t.endsWith("Interface")) return "interface";
671
+ if (t === "ApplicationComponent") return "component";
672
+ if (t === "BusinessProcess" || t === "ApplicationProcess" || t === "TechnologyProcess")
673
+ return "process";
674
+ if (t === "BusinessFunction" || t === "ApplicationFunction" || t === "TechnologyFunction")
675
+ return "function";
676
+ if (t.endsWith("Interaction")) return "interaction";
677
+ if (t.endsWith("Service")) return "service";
678
+ if (t.endsWith("Event")) return "event";
679
+ if (t === "BusinessObject" || t === "DataObject") return "object";
680
+ if (t === "Contract") return "contract";
681
+ if (t === "Representation") return "representation";
682
+ if (t === "Product") return "product";
683
+ if (t === "Artifact") return "artifact";
684
+ if (t === "Node") return "node";
685
+ if (t === "SystemSoftware") return "systemsoftware";
686
+ if (t === "Device") return "device";
687
+ if (t === "Equipment") return "equipment";
688
+ if (t === "Facility") return "facility";
689
+ if (t === "DistributionNetwork") return "distributionnetwork";
690
+ if (t === "CommunicationNetwork") return "network";
691
+ if (t === "Path") return "path";
692
+ if (t === "Material") return "material";
693
+ if (t === "Stakeholder") return "stakeholder";
694
+ if (t === "Driver") return "driver";
695
+ if (t === "Assessment") return "assessment";
696
+ if (t === "Goal") return "goal";
697
+ if (t === "Outcome") return "outcome";
698
+ if (t === "Principle") return "principle";
699
+ if (t === "Requirement") return "requirement";
700
+ if (t === "Constraint") return "constraint";
701
+ if (t === "Meaning") return "meaning";
702
+ if (t === "Value") return "value";
703
+ if (t === "Capability") return "capability";
704
+ if (t === "Resource") return "resource";
705
+ if (t === "CourseOfAction") return "courseofaction";
706
+ if (t === "ValueStream") return "valuestream";
707
+ if (t === "WorkPackage") return "workpackage";
708
+ if (t === "Deliverable") return "deliverable";
709
+ if (t === "Plateau") return "plateau";
710
+ if (t === "Gap") return "gap";
711
+ if (t === "ImplementationEvent") return "event";
712
+ return null;
713
+ }
714
+
715
+ // src/renderer.tsx
716
+ import { jsx, jsxs } from "react/jsx-runtime";
717
+ var MIN_SCALE = 0.05;
718
+ var MAX_SCALE = 50;
719
+ var PAN_FRACTION = 0.2;
720
+ var WHEEL_FACTOR = 1.1;
721
+ var DASH_PATTERN = "6 4";
722
+ var FLOW_PATTERN = "4 3";
723
+ var DOT_PATTERN = "2 3";
724
+ var ArchimateRenderer = forwardRef(
725
+ function ArchimateRenderer2({ view, className, style }, ref) {
726
+ const svgRef = useRef(null);
727
+ const gRef = useRef(null);
728
+ const stateRef = useRef({ tx: 0, ty: 0, scale: 1 });
729
+ const dragRef = useRef(null);
730
+ const nodeMap = useMemo(() => {
731
+ const map = /* @__PURE__ */ new Map();
732
+ for (const n of view.nodes) map.set(n.id, n);
733
+ return map;
734
+ }, [view]);
735
+ const endpointPlan = useMemo(
736
+ () => planEndpoints(view.connections, nodeMap),
737
+ [view, nodeMap]
738
+ );
739
+ function applyTransform() {
740
+ const { tx, ty, scale } = stateRef.current;
741
+ gRef.current?.setAttribute("transform", `translate(${tx} ${ty}) scale(${scale})`);
742
+ }
743
+ function reset() {
744
+ stateRef.current = { tx: 0, ty: 0, scale: 1 };
745
+ applyTransform();
746
+ }
747
+ function zoomAroundViewBox(vbX, vbY, factor) {
748
+ const { tx, ty, scale } = stateRef.current;
749
+ const nextScale = scale * factor;
750
+ if (nextScale < MIN_SCALE || nextScale > MAX_SCALE) return;
751
+ stateRef.current = {
752
+ tx: vbX * (1 - factor) + tx * factor,
753
+ ty: vbY * (1 - factor) + ty * factor,
754
+ scale: nextScale
755
+ };
756
+ applyTransform();
757
+ }
758
+ function clientToViewBox(clientX, clientY) {
759
+ const svg = svgRef.current;
760
+ if (!svg) return null;
761
+ const pt = svg.createSVGPoint();
762
+ pt.x = clientX;
763
+ pt.y = clientY;
764
+ const ctm = svg.getScreenCTM();
765
+ if (!ctm) return null;
766
+ const local = pt.matrixTransform(ctm.inverse());
767
+ return { x: local.x, y: local.y };
768
+ }
769
+ useImperativeHandle(
770
+ ref,
771
+ () => ({
772
+ zoomIn: () => {
773
+ const cx = view.viewBox.x + view.viewBox.width / 2;
774
+ const cy = view.viewBox.y + view.viewBox.height / 2;
775
+ zoomAroundViewBox(cx, cy, 1.25);
776
+ },
777
+ zoomOut: () => {
778
+ const cx = view.viewBox.x + view.viewBox.width / 2;
779
+ const cy = view.viewBox.y + view.viewBox.height / 2;
780
+ zoomAroundViewBox(cx, cy, 1 / 1.25);
781
+ },
782
+ fitView: reset,
783
+ pan: (dirX, dirY) => {
784
+ stateRef.current.tx += -dirX * view.viewBox.width * PAN_FRACTION;
785
+ stateRef.current.ty += -dirY * view.viewBox.height * PAN_FRACTION;
786
+ applyTransform();
787
+ }
788
+ }),
789
+ [view]
790
+ );
791
+ useEffect(() => {
792
+ reset();
793
+ }, [view]);
794
+ useEffect(() => {
795
+ const svg = svgRef.current;
796
+ if (!svg) return;
797
+ const onWheel = (e) => {
798
+ e.preventDefault();
799
+ const local = clientToViewBox(e.clientX, e.clientY);
800
+ if (!local) return;
801
+ const factor = e.deltaY < 0 ? WHEEL_FACTOR : 1 / WHEEL_FACTOR;
802
+ zoomAroundViewBox(local.x, local.y, factor);
803
+ };
804
+ svg.addEventListener("wheel", onWheel, { passive: false });
805
+ return () => svg.removeEventListener("wheel", onWheel);
806
+ }, []);
807
+ function onPointerDown(e) {
808
+ if (e.button !== 0) return;
809
+ const svg = svgRef.current;
810
+ if (!svg) return;
811
+ svg.setPointerCapture(e.pointerId);
812
+ dragRef.current = {
813
+ pointerId: e.pointerId,
814
+ startClientX: e.clientX,
815
+ startClientY: e.clientY,
816
+ startTx: stateRef.current.tx,
817
+ startTy: stateRef.current.ty
818
+ };
819
+ }
820
+ function onPointerMove(e) {
821
+ const drag = dragRef.current;
822
+ if (!drag || drag.pointerId !== e.pointerId) return;
823
+ const start = clientToViewBox(drag.startClientX, drag.startClientY);
824
+ const now = clientToViewBox(e.clientX, e.clientY);
825
+ if (!start || !now) return;
826
+ stateRef.current.tx = drag.startTx + (now.x - start.x);
827
+ stateRef.current.ty = drag.startTy + (now.y - start.y);
828
+ applyTransform();
829
+ }
830
+ function onPointerUp(e) {
831
+ const drag = dragRef.current;
832
+ if (!drag) return;
833
+ svgRef.current?.releasePointerCapture(e.pointerId);
834
+ dragRef.current = null;
835
+ }
836
+ return /* @__PURE__ */ jsx(
837
+ "svg",
838
+ {
839
+ ref: svgRef,
840
+ className,
841
+ style: { touchAction: "none", cursor: "grab", ...style },
842
+ viewBox: `${view.viewBox.x} ${view.viewBox.y} ${view.viewBox.width} ${view.viewBox.height}`,
843
+ preserveAspectRatio: "xMidYMid meet",
844
+ onPointerDown,
845
+ onPointerMove,
846
+ onPointerUp,
847
+ onPointerCancel: onPointerUp,
848
+ children: /* @__PURE__ */ jsxs("g", { ref: gRef, children: [
849
+ view.nodes.map((n) => /* @__PURE__ */ jsx(NodeShape, { node: n }, n.id)),
850
+ view.connections.map((c) => {
851
+ const pts = endpointPlan.get(c.id);
852
+ if (!pts) return null;
853
+ return /* @__PURE__ */ jsx(
854
+ ConnectionPath,
855
+ {
856
+ edge: c,
857
+ sourcePoint: pts.source,
858
+ targetPoint: pts.target
859
+ },
860
+ c.id
861
+ );
862
+ })
863
+ ] })
864
+ }
865
+ );
866
+ }
867
+ );
868
+ var MARKER_GLYPHS = {
869
+ "archi-arrow-filled": {
870
+ refX: 11,
871
+ refY: 5,
872
+ reverseAtStart: true,
873
+ paint: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 12 5 L 0 10 Z", fill: "currentColor" })
874
+ },
875
+ "archi-arrow-open": {
876
+ refX: 11,
877
+ refY: 5,
878
+ reverseAtStart: true,
879
+ paint: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 12 5 L 0 10", fill: "none", stroke: "currentColor", strokeWidth: "1.2" })
880
+ },
881
+ "archi-triangle-hollow": {
882
+ refX: 11,
883
+ refY: 6,
884
+ reverseAtStart: true,
885
+ paint: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 12 6 L 0 12 Z", fill: "white", stroke: "currentColor", strokeWidth: "1" })
886
+ },
887
+ "archi-diamond-filled": {
888
+ refX: 0,
889
+ refY: 5,
890
+ reverseAtStart: false,
891
+ paint: /* @__PURE__ */ jsx(
892
+ "path",
893
+ {
894
+ d: "M 0 5 L 7 0 L 14 5 L 7 10 Z",
895
+ fill: "#1A1A1A",
896
+ stroke: "#1A1A1A",
897
+ strokeWidth: "1"
898
+ }
899
+ )
900
+ },
901
+ "archi-diamond-hollow": {
902
+ refX: 0,
903
+ refY: 5,
904
+ reverseAtStart: false,
905
+ paint: /* @__PURE__ */ jsx(
906
+ "path",
907
+ {
908
+ d: "M 0 5 L 7 0 L 14 5 L 7 10 Z",
909
+ fill: "white",
910
+ stroke: "#1A1A1A",
911
+ strokeWidth: "1"
912
+ }
913
+ )
914
+ },
915
+ "archi-arrow-thin": {
916
+ refX: 9,
917
+ refY: 4,
918
+ reverseAtStart: true,
919
+ paint: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 10 4 L 0 8", fill: "none", stroke: "currentColor", strokeWidth: "1" })
920
+ },
921
+ "archi-dot-filled": {
922
+ refX: 1,
923
+ refY: 4,
924
+ reverseAtStart: false,
925
+ paint: /* @__PURE__ */ jsx("circle", { cx: "4", cy: "4", r: "3", fill: "currentColor" })
926
+ }
927
+ };
928
+ function MarkerInline({
929
+ glyph,
930
+ position,
931
+ pathAngleRad,
932
+ isStart
933
+ }) {
934
+ const spec = MARKER_GLYPHS[glyph];
935
+ if (!spec) return null;
936
+ const angle = isStart && spec.reverseAtStart ? pathAngleRad + Math.PI : pathAngleRad;
937
+ const angleDeg = angle * 180 / Math.PI;
938
+ return /* @__PURE__ */ jsx(
939
+ "g",
940
+ {
941
+ transform: `translate(${position.x} ${position.y}) rotate(${angleDeg}) translate(${-spec.refX} ${-spec.refY})`,
942
+ children: spec.paint
943
+ }
944
+ );
945
+ }
946
+ function styleForEdge(edge) {
947
+ switch (edge.relationshipType) {
948
+ case "Composition":
949
+ return { markerStart: "archi-diamond-filled" };
950
+ case "Aggregation":
951
+ return { markerStart: "archi-diamond-hollow" };
952
+ case "Assignment":
953
+ return { markerStart: "archi-dot-filled", markerEnd: "archi-arrow-filled" };
954
+ case "Realization":
955
+ return { dashArray: DASH_PATTERN, markerEnd: "archi-triangle-hollow" };
956
+ case "Serving":
957
+ case "UsedBy":
958
+ return { markerEnd: "archi-arrow-open" };
959
+ case "Triggering":
960
+ return { markerEnd: "archi-arrow-filled" };
961
+ case "Flow":
962
+ return { dashArray: FLOW_PATTERN, markerEnd: "archi-arrow-filled" };
963
+ case "Specialization":
964
+ return { markerEnd: "archi-triangle-hollow" };
965
+ case "Access": {
966
+ const at = edge.accessType ?? "Access";
967
+ if (at === "Read") return { dashArray: DOT_PATTERN, markerStart: "archi-arrow-thin" };
968
+ if (at === "Write") return { dashArray: DOT_PATTERN, markerEnd: "archi-arrow-thin" };
969
+ if (at === "ReadWrite")
970
+ return {
971
+ dashArray: DOT_PATTERN,
972
+ markerStart: "archi-arrow-thin",
973
+ markerEnd: "archi-arrow-thin"
974
+ };
975
+ return { dashArray: DOT_PATTERN };
976
+ }
977
+ case "Influence":
978
+ return { dashArray: DASH_PATTERN, markerEnd: "archi-arrow-open" };
979
+ case "Association":
980
+ return edge.isDirected ? { markerEnd: "archi-arrow-open" } : {};
981
+ default:
982
+ return { markerEnd: "archi-arrow-filled" };
983
+ }
984
+ }
985
+ function ConnectionPath({
986
+ edge,
987
+ sourcePoint,
988
+ targetPoint
989
+ }) {
990
+ const allPoints = [sourcePoint, ...edge.bendpoints, targetPoint];
991
+ const pointsStr = allPoints.map((p) => `${p.x},${p.y}`).join(" ");
992
+ const style = styleForEdge(edge);
993
+ const stroke = edge.stroke ?? DEFAULT_STROKE;
994
+ const startNeighbor = allPoints[1] ?? sourcePoint;
995
+ const endNeighbor = allPoints[allPoints.length - 2] ?? targetPoint;
996
+ const startAngle = Math.atan2(
997
+ startNeighbor.y - sourcePoint.y,
998
+ startNeighbor.x - sourcePoint.x
999
+ );
1000
+ const endAngle = Math.atan2(
1001
+ targetPoint.y - endNeighbor.y,
1002
+ targetPoint.x - endNeighbor.x
1003
+ );
1004
+ return /* @__PURE__ */ jsxs("g", { style: { color: stroke }, children: [
1005
+ /* @__PURE__ */ jsx(
1006
+ "polyline",
1007
+ {
1008
+ points: pointsStr,
1009
+ fill: "none",
1010
+ stroke,
1011
+ strokeWidth: 1,
1012
+ strokeDasharray: style.dashArray
1013
+ }
1014
+ ),
1015
+ style.markerStart ? /* @__PURE__ */ jsx(
1016
+ MarkerInline,
1017
+ {
1018
+ glyph: style.markerStart,
1019
+ position: sourcePoint,
1020
+ pathAngleRad: startAngle,
1021
+ isStart: true
1022
+ }
1023
+ ) : null,
1024
+ style.markerEnd ? /* @__PURE__ */ jsx(
1025
+ MarkerInline,
1026
+ {
1027
+ glyph: style.markerEnd,
1028
+ position: targetPoint,
1029
+ pathAngleRad: endAngle,
1030
+ isStart: false
1031
+ }
1032
+ ) : null
1033
+ ] });
1034
+ }
1035
+ function pickSide(box, towards) {
1036
+ const cx = box.x + box.w / 2;
1037
+ const cy = box.y + box.h / 2;
1038
+ const dx = towards.x - cx;
1039
+ const dy = towards.y - cy;
1040
+ const ax = Math.abs(dx) / Math.max(box.w / 2, 1);
1041
+ const ay = Math.abs(dy) / Math.max(box.h / 2, 1);
1042
+ if (ax >= ay) return dx >= 0 ? "right" : "left";
1043
+ return dy >= 0 ? "bottom" : "top";
1044
+ }
1045
+ function pointOnSide(box, side, t) {
1046
+ switch (side) {
1047
+ case "left":
1048
+ return { x: box.x, y: box.y + box.h * t };
1049
+ case "right":
1050
+ return { x: box.x + box.w, y: box.y + box.h * t };
1051
+ case "top":
1052
+ return { x: box.x + box.w * t, y: box.y };
1053
+ case "bottom":
1054
+ return { x: box.x + box.w * t, y: box.y + box.h };
1055
+ }
1056
+ }
1057
+ function planEndpoints(connections, nodeMap) {
1058
+ const result = /* @__PURE__ */ new Map();
1059
+ const groups = /* @__PURE__ */ new Map();
1060
+ const addSlot = (nodeId, side, slot) => {
1061
+ let byNode = groups.get(nodeId);
1062
+ if (!byNode) groups.set(nodeId, byNode = /* @__PURE__ */ new Map());
1063
+ let list = byNode.get(side);
1064
+ if (!list) byNode.set(side, list = []);
1065
+ list.push(slot);
1066
+ };
1067
+ for (const edge of connections) {
1068
+ const s = nodeMap.get(edge.sourceId);
1069
+ const t = nodeMap.get(edge.targetId);
1070
+ if (!s || !t) continue;
1071
+ const initial = endpointsForConnection(s, t, edge.bendpoints);
1072
+ result.set(edge.id, initial);
1073
+ if (edge.bendpoints.length > 0) continue;
1074
+ if (s.isJunction || t.isJunction) continue;
1075
+ const tCenter = nodeCenter(t);
1076
+ const sCenter = nodeCenter(s);
1077
+ const sSide = pickSide(s, tCenter);
1078
+ const tSide = pickSide(t, sCenter);
1079
+ const sGuide = sSide === "left" || sSide === "right" ? tCenter.y : tCenter.x;
1080
+ const tGuide = tSide === "left" || tSide === "right" ? sCenter.y : sCenter.x;
1081
+ addSlot(s.id, sSide, { edgeId: edge.id, isSourceEnd: true, guideCoord: sGuide });
1082
+ addSlot(t.id, tSide, { edgeId: edge.id, isSourceEnd: false, guideCoord: tGuide });
1083
+ }
1084
+ for (const [nodeId, byNode] of groups) {
1085
+ const node = nodeMap.get(nodeId);
1086
+ for (const [side, slots] of byNode) {
1087
+ if (slots.length < 2) continue;
1088
+ slots.sort((a, b) => a.guideCoord - b.guideCoord);
1089
+ for (let i = 0; i < slots.length; i++) {
1090
+ const point = pointOnSide(node, side, (i + 1) / (slots.length + 1));
1091
+ const cur = result.get(slots[i].edgeId);
1092
+ result.set(
1093
+ slots[i].edgeId,
1094
+ slots[i].isSourceEnd ? { source: point, target: cur.target } : { source: cur.source, target: point }
1095
+ );
1096
+ }
1097
+ }
1098
+ }
1099
+ return result;
1100
+ }
1101
+ function endpointsForConnection(source, target, bendpoints) {
1102
+ if (source.isJunction && target.isJunction) {
1103
+ return { source: nodeCenter(source), target: nodeCenter(target) };
1104
+ }
1105
+ if (bendpoints.length > 0) {
1106
+ return {
1107
+ source: source.isJunction ? nodeCenter(source) : attachPoint(source, bendpoints[0]),
1108
+ target: target.isJunction ? nodeCenter(target) : attachPoint(target, bendpoints[bendpoints.length - 1])
1109
+ };
1110
+ }
1111
+ const sLeft = source.x;
1112
+ const sRight = source.x + source.w;
1113
+ const sTop = source.y;
1114
+ const sBottom = source.y + source.h;
1115
+ const tLeft = target.x;
1116
+ const tRight = target.x + target.w;
1117
+ const tTop = target.y;
1118
+ const tBottom = target.y + target.h;
1119
+ const yOverlap = Math.min(sBottom, tBottom) - Math.max(sTop, tTop);
1120
+ const xOverlap = Math.min(sRight, tRight) - Math.max(sLeft, tLeft);
1121
+ if (xOverlap < 0 && yOverlap > 0) {
1122
+ const y = (Math.max(sTop, tTop) + Math.min(sBottom, tBottom)) / 2;
1123
+ if (sRight <= tLeft) {
1124
+ return {
1125
+ source: source.isJunction ? nodeCenter(source) : { x: sRight, y },
1126
+ target: target.isJunction ? nodeCenter(target) : { x: tLeft, y }
1127
+ };
1128
+ }
1129
+ return {
1130
+ source: source.isJunction ? nodeCenter(source) : { x: sLeft, y },
1131
+ target: target.isJunction ? nodeCenter(target) : { x: tRight, y }
1132
+ };
1133
+ }
1134
+ if (yOverlap < 0 && xOverlap > 0) {
1135
+ const x = (Math.max(sLeft, tLeft) + Math.min(sRight, tRight)) / 2;
1136
+ if (sBottom <= tTop) {
1137
+ return {
1138
+ source: source.isJunction ? nodeCenter(source) : { x, y: sBottom },
1139
+ target: target.isJunction ? nodeCenter(target) : { x, y: tTop }
1140
+ };
1141
+ }
1142
+ return {
1143
+ source: source.isJunction ? nodeCenter(source) : { x, y: sTop },
1144
+ target: target.isJunction ? nodeCenter(target) : { x, y: tBottom }
1145
+ };
1146
+ }
1147
+ return {
1148
+ source: source.isJunction ? nodeCenter(source) : attachPoint(source, nodeCenter(target)),
1149
+ target: target.isJunction ? nodeCenter(target) : attachPoint(target, nodeCenter(source))
1150
+ };
1151
+ }
1152
+ function nodeCenter(n) {
1153
+ return { x: n.x + n.w / 2, y: n.y + n.h / 2 };
1154
+ }
1155
+ function attachPoint(box, towards) {
1156
+ const left = box.x;
1157
+ const right = box.x + box.w;
1158
+ const top = box.y;
1159
+ const bottom = box.y + box.h;
1160
+ const xOut = towards.x < left ? -1 : towards.x > right ? 1 : 0;
1161
+ const yOut = towards.y < top ? -1 : towards.y > bottom ? 1 : 0;
1162
+ if (xOut !== 0 && yOut === 0) {
1163
+ return { x: xOut < 0 ? left : right, y: clamp(towards.y, top, bottom) };
1164
+ }
1165
+ if (xOut === 0 && yOut !== 0) {
1166
+ return { x: clamp(towards.x, left, right), y: yOut < 0 ? top : bottom };
1167
+ }
1168
+ if (xOut !== 0 && yOut !== 0) {
1169
+ const cx = (left + right) / 2;
1170
+ const cy = (top + bottom) / 2;
1171
+ const dx = towards.x - cx;
1172
+ const dy = towards.y - cy;
1173
+ const t = Math.min(box.w / 2 / Math.abs(dx), box.h / 2 / Math.abs(dy));
1174
+ return { x: cx + dx * t, y: cy + dy * t };
1175
+ }
1176
+ return nodeCenter(box);
1177
+ }
1178
+ function clamp(v, lo, hi) {
1179
+ return Math.max(lo, Math.min(hi, v));
1180
+ }
1181
+ function NodeShape({ node }) {
1182
+ if (node.isJunction) return /* @__PURE__ */ jsx(JunctionShape, { node });
1183
+ if (node.nodeType === "Label") return /* @__PURE__ */ jsx(LabelShape, { node });
1184
+ const isContainer = node.nodeType === "Container" || node.nodeType === "Group" || !node.elementId && !node.nodeType;
1185
+ return /* @__PURE__ */ jsx(ElementShape, { node, dashed: isContainer, transparent: isContainer });
1186
+ }
1187
+ function JunctionShape({ node }) {
1188
+ const cx = node.x + node.w / 2;
1189
+ const cy = node.y + node.h / 2;
1190
+ const r = Math.max(4, Math.min(node.w, node.h) / 2);
1191
+ return /* @__PURE__ */ jsx("circle", { cx, cy, r, fill: node.stroke });
1192
+ }
1193
+ function LabelShape({ node }) {
1194
+ if (!node.label) return null;
1195
+ const fontSize = node.fontSize ?? 11;
1196
+ const textX = node.x + node.w / 2;
1197
+ const lines = wrapLabel(node.label, node.w - 8, fontSize);
1198
+ const lineHeight = fontSize * 1.2;
1199
+ const blockHeight = lineHeight * (lines.length - 1);
1200
+ const firstY = node.y + node.h / 2 + fontSize / 3 - blockHeight / 2;
1201
+ return /* @__PURE__ */ jsx(
1202
+ "text",
1203
+ {
1204
+ x: textX,
1205
+ y: firstY,
1206
+ textAnchor: "middle",
1207
+ fontFamily: node.fontFamily ?? "system-ui, -apple-system, sans-serif",
1208
+ fontSize,
1209
+ fill: node.textColor,
1210
+ children: lines.map((line, i) => /* @__PURE__ */ jsx("tspan", { x: textX, dy: i === 0 ? 0 : lineHeight, children: line }, i))
1211
+ }
1212
+ );
1213
+ }
1214
+ var MOTIVATION_TYPES = /* @__PURE__ */ new Set([
1215
+ "Stakeholder",
1216
+ "Driver",
1217
+ "Assessment",
1218
+ "Goal",
1219
+ "Outcome",
1220
+ "Principle",
1221
+ "Requirement",
1222
+ "Constraint",
1223
+ "Meaning",
1224
+ "Value"
1225
+ ]);
1226
+ var STRATEGY_TYPES = /* @__PURE__ */ new Set(["Capability", "Resource", "CourseOfAction", "ValueStream"]);
1227
+ var IMPLEMENTATION_TYPES = /* @__PURE__ */ new Set([
1228
+ "WorkPackage",
1229
+ "Deliverable",
1230
+ "Plateau",
1231
+ "Gap",
1232
+ "ImplementationEvent"
1233
+ ]);
1234
+ function shapeForType(elementType, h) {
1235
+ if (!elementType) return { kind: "rect", rx: 4 };
1236
+ if (elementType.endsWith("Service")) return { kind: "stadium" };
1237
+ if (elementType.endsWith("Process") || elementType.endsWith("Function") || elementType.endsWith("Interaction") || elementType.endsWith("Event"))
1238
+ return { kind: "rect", rx: Math.min(14, h / 3) };
1239
+ if (MOTIVATION_TYPES.has(elementType)) return { kind: "rect", rx: Math.min(18, h / 2.5) };
1240
+ if (STRATEGY_TYPES.has(elementType)) return { kind: "rect", rx: Math.min(12, h / 3) };
1241
+ if (IMPLEMENTATION_TYPES.has(elementType)) return { kind: "rect", rx: 6 };
1242
+ return { kind: "rect", rx: 2 };
1243
+ }
1244
+ function ElementShape({
1245
+ node,
1246
+ dashed,
1247
+ transparent
1248
+ }) {
1249
+ const fontSize = node.fontSize ?? 11;
1250
+ const fill = transparent ? "none" : node.fill;
1251
+ const iconName = iconNameForType(node.elementType);
1252
+ const showIcon = !!iconName && node.w >= 50 && node.h >= 28;
1253
+ const iconSize = showIcon ? Math.min(22, Math.max(14, node.w * 0.18)) : 0;
1254
+ const shape = shapeForType(node.elementType, node.h);
1255
+ const radius = shape.kind === "stadium" ? node.h / 2 : shape.rx;
1256
+ return /* @__PURE__ */ jsxs("g", { style: { color: node.stroke }, children: [
1257
+ /* @__PURE__ */ jsx(
1258
+ "rect",
1259
+ {
1260
+ x: node.x,
1261
+ y: node.y,
1262
+ width: node.w,
1263
+ height: node.h,
1264
+ fill,
1265
+ stroke: node.stroke,
1266
+ strokeWidth: 1,
1267
+ strokeDasharray: dashed ? "5 4" : void 0,
1268
+ rx: radius,
1269
+ ry: radius
1270
+ }
1271
+ ),
1272
+ showIcon ? /* @__PURE__ */ jsx(
1273
+ IconBadge,
1274
+ {
1275
+ x: node.x + node.w - iconSize - 5,
1276
+ y: node.y + 5,
1277
+ size: iconSize,
1278
+ iconName,
1279
+ color: node.stroke
1280
+ }
1281
+ ) : null,
1282
+ node.label ? (() => {
1283
+ const textX = node.x + node.w / 2;
1284
+ const textY = node.y + Math.min(16, node.h / 2 + fontSize / 2);
1285
+ const availWidth = node.w - (showIcon ? iconSize + 12 : 8);
1286
+ const lines = wrapLabel(node.label, availWidth, fontSize);
1287
+ return /* @__PURE__ */ jsx(
1288
+ "text",
1289
+ {
1290
+ x: textX,
1291
+ y: textY,
1292
+ textAnchor: "middle",
1293
+ fontFamily: node.fontFamily ?? "system-ui, -apple-system, sans-serif",
1294
+ fontSize,
1295
+ fill: node.textColor,
1296
+ children: lines.map((line, i) => /* @__PURE__ */ jsx("tspan", { x: textX, dy: i === 0 ? 0 : fontSize * 1.2, children: line }, i))
1297
+ }
1298
+ );
1299
+ })() : null
1300
+ ] });
1301
+ }
1302
+ function IconBadge({
1303
+ x,
1304
+ y,
1305
+ size,
1306
+ iconName,
1307
+ color
1308
+ }) {
1309
+ const def = ICONS[iconName];
1310
+ if (!def) return null;
1311
+ const scale = size / 100;
1312
+ return /* @__PURE__ */ jsxs(
1313
+ "g",
1314
+ {
1315
+ transform: `translate(${x} ${y}) scale(${scale})`,
1316
+ stroke: color,
1317
+ strokeWidth: 5,
1318
+ fill: "none",
1319
+ strokeLinejoin: "round",
1320
+ strokeLinecap: "round",
1321
+ children: [
1322
+ def.outline?.map((d, i) => /* @__PURE__ */ jsx("path", { d }, `o-${i}`)),
1323
+ def.filled?.map((d, i) => /* @__PURE__ */ jsx("path", { d, fill: color, stroke: "none" }, `f-${i}`))
1324
+ ]
1325
+ }
1326
+ );
1327
+ }
1328
+ function wrapLabel(label, width, fontSize, maxLines = 3) {
1329
+ const maxChars = Math.max(1, Math.floor(width / (fontSize * 0.55)));
1330
+ const words = label.split(/\s+/).filter(Boolean);
1331
+ const lines = [];
1332
+ let current = "";
1333
+ for (const word of words) {
1334
+ const candidate = current ? `${current} ${word}` : word;
1335
+ if (candidate.length <= maxChars) {
1336
+ current = candidate;
1337
+ continue;
1338
+ }
1339
+ if (current) lines.push(current);
1340
+ if (word.length > maxChars) {
1341
+ let rest = word;
1342
+ while (rest.length > maxChars) {
1343
+ lines.push(rest.slice(0, maxChars));
1344
+ rest = rest.slice(maxChars);
1345
+ }
1346
+ current = rest;
1347
+ } else {
1348
+ current = word;
1349
+ }
1350
+ }
1351
+ if (current) lines.push(current);
1352
+ if (lines.length > maxLines) {
1353
+ const truncated = lines.slice(0, maxLines);
1354
+ const last = truncated.length - 1;
1355
+ truncated[last] = truncated[last].slice(0, Math.max(1, maxChars - 1)) + "\u2026";
1356
+ return truncated;
1357
+ }
1358
+ return lines;
1359
+ }
1360
+ export {
1361
+ ArchimateParseError,
1362
+ ArchimateRenderer,
1363
+ ICONS,
1364
+ LAYER_COLORS,
1365
+ colorForType,
1366
+ iconNameForType,
1367
+ parseArchimate
1368
+ };