@dr2rai/raid-canvas 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/RaiBridge.d.ts +10 -3
- package/dist/RaiBridge.d.ts.map +1 -1
- package/dist/RaiBridge.js +252 -22
- package/dist/RaiBridge.js.map +1 -1
- package/dist/RaidCanvas.d.ts +2 -0
- package/dist/RaidCanvas.d.ts.map +1 -1
- package/dist/RaidCanvas.js +39 -10
- package/dist/RaidCanvas.js.map +1 -1
- package/dist/X6Shapes.d.ts +7 -0
- package/dist/X6Shapes.d.ts.map +1 -1
- package/dist/X6Shapes.js +146 -22
- package/dist/X6Shapes.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/RaiBridge.ts +294 -25
- package/src/RaidCanvas.tsx +41 -7
- package/src/X6Shapes.ts +151 -22
- package/src/index.ts +1 -0
- package/src/styles/aoaim-theme.css +21 -7
- package/src/types.ts +16 -0
package/src/RaidCanvas.tsx
CHANGED
|
@@ -57,6 +57,8 @@ export interface RaidCanvasProps {
|
|
|
57
57
|
onSelectionChange?: (selectedIds: string[]) => void;
|
|
58
58
|
/** Callback fired when a stencil is dropped onto the canvas */
|
|
59
59
|
onDropStencil?: (kind: AimOntologyKind, point: { x: number; y: number }) => void;
|
|
60
|
+
/** Callback fired when the active routing mode changes (e.g. hydrated from SVG or switched by user) */
|
|
61
|
+
onRoutingModeChange?: (mode: AimRoutingMode) => void;
|
|
60
62
|
/** Whether to render built-in navigation controls (Zoom In/Out, Fit, Center, Reset). Default: true */
|
|
61
63
|
showToolbar?: boolean;
|
|
62
64
|
}
|
|
@@ -115,6 +117,7 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
115
117
|
onSelect,
|
|
116
118
|
onSelectionChange,
|
|
117
119
|
onDropStencil,
|
|
120
|
+
onRoutingModeChange,
|
|
118
121
|
showToolbar = true,
|
|
119
122
|
},
|
|
120
123
|
ref,
|
|
@@ -128,7 +131,6 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
128
131
|
const selectedCellIdRef = useRef<string | null>(null);
|
|
129
132
|
const clearEdgeToolsRef = useRef<(() => void) | null>(null);
|
|
130
133
|
const routingModeRef = useRef<AimRoutingMode>(defaultRouting);
|
|
131
|
-
routingModeRef.current = defaultRouting;
|
|
132
134
|
|
|
133
135
|
const [zoomLevel, setZoomLevel] = useState<number>(100);
|
|
134
136
|
const [isDragOver, setIsDragOver] = useState<boolean>(false);
|
|
@@ -152,6 +154,9 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
152
154
|
const onDropStencilRef = useRef(onDropStencil);
|
|
153
155
|
onDropStencilRef.current = onDropStencil;
|
|
154
156
|
|
|
157
|
+
const onRoutingModeChangeRef = useRef(onRoutingModeChange);
|
|
158
|
+
onRoutingModeChangeRef.current = onRoutingModeChange;
|
|
159
|
+
|
|
155
160
|
const svgPropRef = useRef(activeSvg);
|
|
156
161
|
svgPropRef.current = activeSvg;
|
|
157
162
|
|
|
@@ -169,6 +174,7 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
169
174
|
const updatedSvg = bridgeRef.current.serializeToSvg(
|
|
170
175
|
graphRef.current,
|
|
171
176
|
currentBaseSvg,
|
|
177
|
+
{ routingMode: routingModeRef.current },
|
|
172
178
|
);
|
|
173
179
|
lastSerializedSvgRef.current = updatedSvg;
|
|
174
180
|
|
|
@@ -227,6 +233,7 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
227
233
|
return bridgeRef.current.serializeToSvg(
|
|
228
234
|
graphRef.current,
|
|
229
235
|
svgPropRef.current,
|
|
236
|
+
{ routingMode: routingModeRef.current },
|
|
230
237
|
);
|
|
231
238
|
},
|
|
232
239
|
addNode: (kind, x, y, customData) => {
|
|
@@ -310,8 +317,7 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
310
317
|
if (!edge || !edge.isEdge()) return;
|
|
311
318
|
|
|
312
319
|
const currentData = (edge.getData() ?? {}) as RaidEdgeData;
|
|
313
|
-
const
|
|
314
|
-
edge.setData(nextData);
|
|
320
|
+
const mutableData: Record<string, unknown> = { ...currentData, ...updates };
|
|
315
321
|
|
|
316
322
|
if (updates.routing !== undefined) {
|
|
317
323
|
applyEdgeRouting(edge, updates.routing);
|
|
@@ -320,17 +326,31 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
320
326
|
if (updates.sourcePort !== undefined) {
|
|
321
327
|
const currentSource = edge.getSource() as { cell?: string };
|
|
322
328
|
if (currentSource.cell) {
|
|
323
|
-
|
|
329
|
+
if (updates.sourcePort === '' || updates.sourcePort === 'auto') {
|
|
330
|
+
edge.setSource({ cell: currentSource.cell });
|
|
331
|
+
delete mutableData.sourcePort;
|
|
332
|
+
} else {
|
|
333
|
+
edge.setSource({ cell: currentSource.cell, port: updates.sourcePort });
|
|
334
|
+
mutableData.sourcePort = updates.sourcePort;
|
|
335
|
+
}
|
|
324
336
|
}
|
|
325
337
|
}
|
|
326
338
|
|
|
327
339
|
if (updates.targetPort !== undefined) {
|
|
328
340
|
const currentTarget = edge.getTarget() as { cell?: string };
|
|
329
341
|
if (currentTarget.cell) {
|
|
330
|
-
|
|
342
|
+
if (updates.targetPort === '' || updates.targetPort === 'auto') {
|
|
343
|
+
edge.setTarget({ cell: currentTarget.cell });
|
|
344
|
+
delete mutableData.targetPort;
|
|
345
|
+
} else {
|
|
346
|
+
edge.setTarget({ cell: currentTarget.cell, port: updates.targetPort });
|
|
347
|
+
mutableData.targetPort = updates.targetPort;
|
|
348
|
+
}
|
|
331
349
|
}
|
|
332
350
|
}
|
|
333
351
|
|
|
352
|
+
edge.setData(mutableData as unknown as RaidEdgeData);
|
|
353
|
+
|
|
334
354
|
if (updates.label !== undefined || updates.stereotype !== undefined) {
|
|
335
355
|
const text = updates.label ?? updates.stereotype ?? '';
|
|
336
356
|
edge.setLabels(
|
|
@@ -355,6 +375,10 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
355
375
|
},
|
|
356
376
|
setRoutingMode: (mode, applyToAll = true) => {
|
|
357
377
|
routingModeRef.current = mode;
|
|
378
|
+
if (graphRef.current) {
|
|
379
|
+
(graphRef.current as unknown as { _aimRoutingMode?: AimRoutingMode })._aimRoutingMode = mode;
|
|
380
|
+
}
|
|
381
|
+
onRoutingModeChangeRef.current?.(mode);
|
|
358
382
|
if (applyToAll && graphRef.current) {
|
|
359
383
|
for (const edge of graphRef.current.getEdges()) {
|
|
360
384
|
applyEdgeRouting(edge, mode);
|
|
@@ -723,8 +747,13 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
723
747
|
if (svgPropRef.current) {
|
|
724
748
|
isHydratingRef.current = true;
|
|
725
749
|
try {
|
|
726
|
-
bridgeRef.current.hydrateFromSvg(svgPropRef.current, graph);
|
|
750
|
+
const metamodel = bridgeRef.current.hydrateFromSvg(svgPropRef.current, graph, { inferPorts: false });
|
|
727
751
|
lastSerializedSvgRef.current = svgPropRef.current;
|
|
752
|
+
if (metamodel.routing) {
|
|
753
|
+
routingModeRef.current = metamodel.routing;
|
|
754
|
+
(graph as unknown as { _aimRoutingMode?: AimRoutingMode })._aimRoutingMode = metamodel.routing;
|
|
755
|
+
onRoutingModeChangeRef.current?.(metamodel.routing);
|
|
756
|
+
}
|
|
728
757
|
graph.centerContent();
|
|
729
758
|
} catch (err) {
|
|
730
759
|
console.error('RaidCanvas hydration error:', err);
|
|
@@ -760,8 +789,13 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
760
789
|
|
|
761
790
|
isHydratingRef.current = true;
|
|
762
791
|
try {
|
|
763
|
-
bridgeRef.current.hydrateFromSvg(activeSvg, graph);
|
|
792
|
+
const metamodel = bridgeRef.current.hydrateFromSvg(activeSvg, graph, { inferPorts: false });
|
|
764
793
|
lastSerializedSvgRef.current = activeSvg;
|
|
794
|
+
if (metamodel.routing) {
|
|
795
|
+
routingModeRef.current = metamodel.routing;
|
|
796
|
+
(graph as unknown as { _aimRoutingMode?: AimRoutingMode })._aimRoutingMode = metamodel.routing;
|
|
797
|
+
onRoutingModeChangeRef.current?.(metamodel.routing);
|
|
798
|
+
}
|
|
765
799
|
graph.centerContent();
|
|
766
800
|
} catch (err) {
|
|
767
801
|
console.error('RaidCanvas re-hydration error:', err);
|
package/src/X6Shapes.ts
CHANGED
|
@@ -37,6 +37,74 @@ export const CascaisPalette = {
|
|
|
37
37
|
TextSecondary: '#4B5563',
|
|
38
38
|
} as const;
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Formats and wraps node label text for AOAIM entities.
|
|
42
|
+
* Automatically wraps on whitespace when exceeding target length,
|
|
43
|
+
* and treats `<wbr>` / `<wbr/>` tags and hyphens as soft word-break opportunities
|
|
44
|
+
* within long unbroken words or strings.
|
|
45
|
+
*/
|
|
46
|
+
export function wrapAimText(rawText: string, maxLineLength: number = 18): string {
|
|
47
|
+
if (!rawText) return '';
|
|
48
|
+
|
|
49
|
+
const lines = rawText.split('\n');
|
|
50
|
+
const resultLines: string[] = [];
|
|
51
|
+
|
|
52
|
+
for (const line of lines) {
|
|
53
|
+
if (!line) {
|
|
54
|
+
resultLines.push('');
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Replace <wbr> / <wbr/> with zero-width break marker \u200B,
|
|
59
|
+
// and allow breaking after hyphens within words
|
|
60
|
+
const normalized = line
|
|
61
|
+
.replace(/<wbr\s*\/?>/gi, '\u200B')
|
|
62
|
+
.replace(/-(?=[a-zA-Z0-9])/g, '-\u200B');
|
|
63
|
+
|
|
64
|
+
const spaceWords = normalized.split(/\s+/).filter(Boolean);
|
|
65
|
+
if (spaceWords.length === 0) continue;
|
|
66
|
+
|
|
67
|
+
let currentLine = '';
|
|
68
|
+
|
|
69
|
+
for (let wordIdx = 0; wordIdx < spaceWords.length; wordIdx++) {
|
|
70
|
+
const spaceWord = spaceWords[wordIdx]!;
|
|
71
|
+
const chunks = spaceWord.split('\u200B').filter(Boolean);
|
|
72
|
+
|
|
73
|
+
for (let chunkIdx = 0; chunkIdx < chunks.length; chunkIdx++) {
|
|
74
|
+
const chunk = chunks[chunkIdx]!;
|
|
75
|
+
const isFirstChunkOfWord = chunkIdx === 0;
|
|
76
|
+
|
|
77
|
+
if (!currentLine) {
|
|
78
|
+
currentLine = chunk;
|
|
79
|
+
} else if (isFirstChunkOfWord) {
|
|
80
|
+
// Break or space before a new whitespace-separated word
|
|
81
|
+
if (currentLine.length + 1 + chunk.length <= maxLineLength) {
|
|
82
|
+
currentLine += ' ' + chunk;
|
|
83
|
+
} else {
|
|
84
|
+
resultLines.push(currentLine);
|
|
85
|
+
currentLine = chunk;
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
// Soft-break opportunity within a word (<wbr> or hyphen):
|
|
89
|
+
// Glues together without space if it fits; breaks without space if it overflows
|
|
90
|
+
if (currentLine.length + chunk.length <= maxLineLength) {
|
|
91
|
+
currentLine += chunk;
|
|
92
|
+
} else {
|
|
93
|
+
resultLines.push(currentLine);
|
|
94
|
+
currentLine = chunk;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (currentLine) {
|
|
101
|
+
resultLines.push(currentLine);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return resultLines.join('\n');
|
|
106
|
+
}
|
|
107
|
+
|
|
40
108
|
/**
|
|
41
109
|
* Port configuration generating 4 orthogonal snap anchors.
|
|
42
110
|
*/
|
|
@@ -133,7 +201,7 @@ export function registerAimShapes(): void {
|
|
|
133
201
|
ports: createOrthogonalPorts(),
|
|
134
202
|
});
|
|
135
203
|
|
|
136
|
-
// 2. AimActivityNode ('act') — Rounded rectangle with Heraldic Green border
|
|
204
|
+
// 2. AimActivityNode ('act') — Rounded rectangle with Heraldic Green border & underlined label
|
|
137
205
|
Shape.Rect.define({
|
|
138
206
|
shape: 'aim-act',
|
|
139
207
|
overwrite: true,
|
|
@@ -156,6 +224,11 @@ export function registerAimShapes(): void {
|
|
|
156
224
|
fontFamily: 'Inter, system-ui, -apple-system, sans-serif',
|
|
157
225
|
textAnchor: 'middle',
|
|
158
226
|
textVerticalAnchor: 'middle',
|
|
227
|
+
textDecoration: 'underline',
|
|
228
|
+
textWrap: {
|
|
229
|
+
width: -16,
|
|
230
|
+
breakWord: true,
|
|
231
|
+
},
|
|
159
232
|
},
|
|
160
233
|
},
|
|
161
234
|
ports: createOrthogonalPorts(),
|
|
@@ -278,34 +351,77 @@ export function registerAimShapes(): void {
|
|
|
278
351
|
fontFamily: 'Inter, system-ui, -apple-system, sans-serif',
|
|
279
352
|
textAnchor: 'middle',
|
|
280
353
|
textVerticalAnchor: 'middle',
|
|
354
|
+
textDecoration: 'underline',
|
|
355
|
+
textWrap: {
|
|
356
|
+
width: -16,
|
|
357
|
+
breakWord: true,
|
|
358
|
+
},
|
|
281
359
|
},
|
|
282
360
|
},
|
|
283
361
|
ports: createOrthogonalPorts(),
|
|
284
362
|
});
|
|
285
363
|
|
|
286
|
-
// 5. AimPersonNode ('per') — Person / Actor
|
|
364
|
+
// 5. AimPersonNode ('per') — Person / Actor glyph
|
|
287
365
|
Shape.Rect.define({
|
|
288
366
|
shape: 'aim-per',
|
|
289
367
|
overwrite: true,
|
|
290
|
-
width:
|
|
291
|
-
height:
|
|
368
|
+
width: 90,
|
|
369
|
+
height: 90,
|
|
370
|
+
markup: [
|
|
371
|
+
{
|
|
372
|
+
tagName: 'rect',
|
|
373
|
+
selector: 'body',
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
tagName: 'path',
|
|
377
|
+
selector: 'torso',
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
tagName: 'circle',
|
|
381
|
+
selector: 'head',
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
tagName: 'text',
|
|
385
|
+
selector: 'label',
|
|
386
|
+
},
|
|
387
|
+
],
|
|
292
388
|
attrs: {
|
|
293
389
|
body: {
|
|
294
|
-
fill:
|
|
295
|
-
stroke:
|
|
296
|
-
strokeWidth:
|
|
297
|
-
rx: 6,
|
|
298
|
-
ry: 6,
|
|
390
|
+
fill: 'transparent',
|
|
391
|
+
stroke: 'transparent',
|
|
392
|
+
strokeWidth: 0,
|
|
299
393
|
class: 'aim-node aim-per',
|
|
300
394
|
},
|
|
395
|
+
torso: {
|
|
396
|
+
d: 'M 61 50 v -4 a 8 8 0 0 0 -8 -8 H 37 a 8 8 0 0 0 -8 8 v 4',
|
|
397
|
+
fill: 'none',
|
|
398
|
+
stroke: CascaisPalette.WarmGraphite,
|
|
399
|
+
strokeWidth: 2,
|
|
400
|
+
strokeLinecap: 'round',
|
|
401
|
+
strokeLinejoin: 'round',
|
|
402
|
+
},
|
|
403
|
+
head: {
|
|
404
|
+
cx: 45,
|
|
405
|
+
cy: 22,
|
|
406
|
+
r: 8,
|
|
407
|
+
fill: CascaisPalette.ChalkWhite,
|
|
408
|
+
stroke: CascaisPalette.WarmGraphite,
|
|
409
|
+
strokeWidth: 2,
|
|
410
|
+
},
|
|
301
411
|
label: {
|
|
302
|
-
text: '
|
|
412
|
+
text: 'Actor',
|
|
303
413
|
fill: CascaisPalette.TextPrimary,
|
|
304
414
|
fontSize: 12,
|
|
305
415
|
fontWeight: '500',
|
|
306
416
|
fontFamily: 'Inter, system-ui, -apple-system, sans-serif',
|
|
307
417
|
textAnchor: 'middle',
|
|
308
|
-
textVerticalAnchor: '
|
|
418
|
+
textVerticalAnchor: 'top',
|
|
419
|
+
refX: 0.5,
|
|
420
|
+
refY: 62,
|
|
421
|
+
textWrap: {
|
|
422
|
+
width: -10,
|
|
423
|
+
breakWord: true,
|
|
424
|
+
},
|
|
309
425
|
},
|
|
310
426
|
},
|
|
311
427
|
ports: createOrthogonalPorts(),
|
|
@@ -391,25 +507,30 @@ export function createAimNode(data: RaidNodeData): Node.Metadata {
|
|
|
391
507
|
|
|
392
508
|
// Archetype-specific customization
|
|
393
509
|
switch (data.kind) {
|
|
394
|
-
case 'uc':
|
|
510
|
+
case 'uc': {
|
|
511
|
+
const wrappedName = wrapAimText(data.displayName);
|
|
395
512
|
return {
|
|
396
513
|
...baseMetadata,
|
|
397
514
|
attrs: {
|
|
398
515
|
label: {
|
|
399
|
-
text: data.stereotype ? `${data.stereotype}\n${
|
|
516
|
+
text: data.stereotype ? `${data.stereotype}\n${wrappedName}` : wrappedName,
|
|
400
517
|
},
|
|
401
518
|
},
|
|
402
519
|
};
|
|
520
|
+
}
|
|
403
521
|
|
|
404
|
-
case 'act':
|
|
522
|
+
case 'act': {
|
|
523
|
+
const wrappedName = wrapAimText(data.displayName);
|
|
405
524
|
return {
|
|
406
525
|
...baseMetadata,
|
|
407
526
|
attrs: {
|
|
408
527
|
label: {
|
|
409
|
-
text: data.stereotype ? `${data.stereotype}\n${
|
|
528
|
+
text: data.stereotype ? `${data.stereotype}\n${wrappedName}` : wrappedName,
|
|
529
|
+
textDecoration: 'underline',
|
|
410
530
|
},
|
|
411
531
|
},
|
|
412
532
|
};
|
|
533
|
+
}
|
|
413
534
|
|
|
414
535
|
case 'cls':
|
|
415
536
|
return {
|
|
@@ -427,27 +548,35 @@ export function createAimNode(data: RaidNodeData): Node.Metadata {
|
|
|
427
548
|
},
|
|
428
549
|
};
|
|
429
550
|
|
|
430
|
-
case 'obj':
|
|
551
|
+
case 'obj': {
|
|
552
|
+
const wrappedName = wrapAimText(data.displayName);
|
|
431
553
|
return {
|
|
432
554
|
...baseMetadata,
|
|
433
555
|
attrs: {
|
|
434
556
|
label: {
|
|
435
|
-
text: data.
|
|
557
|
+
text: data.stereotype ? `${data.stereotype}\n${wrappedName}` : wrappedName,
|
|
558
|
+
textDecoration: 'underline',
|
|
436
559
|
},
|
|
437
560
|
},
|
|
438
561
|
};
|
|
562
|
+
}
|
|
439
563
|
|
|
440
564
|
case 'per': {
|
|
441
565
|
const isInitiating = data.stereotype?.toLowerCase().includes('initiates') ?? false;
|
|
566
|
+
const strokeColor = isInitiating ? CascaisPalette.NetGold : CascaisPalette.WarmGraphite;
|
|
567
|
+
const wrappedName = wrapAimText(data.displayName, 14);
|
|
568
|
+
const text = data.stereotype ? `${data.stereotype}\n${wrappedName}` : wrappedName;
|
|
442
569
|
return {
|
|
443
570
|
...baseMetadata,
|
|
444
571
|
attrs: {
|
|
445
|
-
|
|
446
|
-
stroke:
|
|
447
|
-
|
|
572
|
+
torso: {
|
|
573
|
+
stroke: strokeColor,
|
|
574
|
+
},
|
|
575
|
+
head: {
|
|
576
|
+
stroke: strokeColor,
|
|
448
577
|
},
|
|
449
578
|
label: {
|
|
450
|
-
text
|
|
579
|
+
text,
|
|
451
580
|
},
|
|
452
581
|
},
|
|
453
582
|
};
|
|
@@ -648,7 +777,7 @@ export function getDefaultNodeBounds(
|
|
|
648
777
|
case 'obj':
|
|
649
778
|
return { x, y, width: 160, height: 80 };
|
|
650
779
|
case 'per':
|
|
651
|
-
return { x, y, width:
|
|
780
|
+
return { x, y, width: 90, height: 90 };
|
|
652
781
|
default:
|
|
653
782
|
return { x, y, width: 140, height: 60 };
|
|
654
783
|
}
|
package/src/index.ts
CHANGED
|
@@ -83,6 +83,12 @@
|
|
|
83
83
|
ry: var(--aim-radius-act);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
.aim-node[aim-act] text,
|
|
87
|
+
.aim-node[aim-kind="act"] text,
|
|
88
|
+
.aim-act text {
|
|
89
|
+
text-decoration: underline;
|
|
90
|
+
}
|
|
91
|
+
|
|
86
92
|
.aim-node[aim-act]:hover,
|
|
87
93
|
.aim-node[aim-kind="act"]:hover {
|
|
88
94
|
filter: drop-shadow(0 0 6px rgba(16, 185, 129, 0.4));
|
|
@@ -115,20 +121,28 @@
|
|
|
115
121
|
stroke-width: 1.5px;
|
|
116
122
|
}
|
|
117
123
|
|
|
124
|
+
.aim-node[aim-obj] text,
|
|
125
|
+
.aim-node[aim-kind="obj"] text,
|
|
126
|
+
.aim-obj text {
|
|
127
|
+
text-decoration: underline;
|
|
128
|
+
}
|
|
129
|
+
|
|
118
130
|
/* 5. Person / Actor Nodes ('per') */
|
|
119
131
|
.aim-node[aim-per],
|
|
120
132
|
.aim-node[aim-kind="per"],
|
|
121
133
|
.aim-per {
|
|
122
|
-
fill:
|
|
123
|
-
stroke:
|
|
124
|
-
stroke-width: 1.5px;
|
|
125
|
-
rx: var(--aim-radius-card);
|
|
126
|
-
ry: var(--aim-radius-card);
|
|
134
|
+
fill: transparent;
|
|
135
|
+
stroke: none;
|
|
127
136
|
}
|
|
128
137
|
|
|
129
|
-
.aim-node[aim-per]
|
|
138
|
+
.aim-node[aim-per] path,
|
|
139
|
+
.aim-node[aim-per] circle {
|
|
140
|
+
transition: stroke 0.15s ease;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.aim-node[aim-per][aim-role="initiating"] path,
|
|
144
|
+
.aim-node[aim-per][aim-role="initiating"] circle {
|
|
130
145
|
stroke: var(--aim-net-gold);
|
|
131
|
-
stroke-width: 2px;
|
|
132
146
|
}
|
|
133
147
|
|
|
134
148
|
/* Relationship Edges */
|
package/src/types.ts
CHANGED
|
@@ -139,6 +139,9 @@ export interface RaidEdgeData {
|
|
|
139
139
|
|
|
140
140
|
/** User-editable or router-computed Manhattan bend points. */
|
|
141
141
|
readonly bendPoints: readonly SvgBendPoint[];
|
|
142
|
+
|
|
143
|
+
/** Precomputed or live SVG path data ('M ... L ...') for standalone vector rendering. */
|
|
144
|
+
readonly pathData?: string;
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
/**
|
|
@@ -160,6 +163,9 @@ export interface RaidMetamodel {
|
|
|
160
163
|
/** Relationships connecting projected nodes. */
|
|
161
164
|
readonly edges: readonly RaidEdgeData[];
|
|
162
165
|
|
|
166
|
+
/** Diagram-level edge routing mode ('manhattan', 'normal', 'smooth'). */
|
|
167
|
+
readonly routing?: AimRoutingMode;
|
|
168
|
+
|
|
163
169
|
/** Additional diagram-level metadata. */
|
|
164
170
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
165
171
|
}
|
|
@@ -201,6 +207,13 @@ export interface HydrationOptions {
|
|
|
201
207
|
/** If true, automatically executes Manhattan routing if bend points are missing. Default: true. */
|
|
202
208
|
readonly autoRouteEdges?: boolean;
|
|
203
209
|
|
|
210
|
+
/**
|
|
211
|
+
* If true, infers docking ports (e.g. port-right, port-left) based on node geometry when ports
|
|
212
|
+
* are omitted in the SVG. When false (default), edges bind directly to node cells without ports,
|
|
213
|
+
* enabling dynamic Manhattan center-aiming routing. Default: false.
|
|
214
|
+
*/
|
|
215
|
+
readonly inferPorts?: boolean;
|
|
216
|
+
|
|
204
217
|
/** Default fallback dimensions when width/height are unspecified in SVG. */
|
|
205
218
|
readonly defaultNodeSize?: { readonly width: number; readonly height: number };
|
|
206
219
|
}
|
|
@@ -217,4 +230,7 @@ export interface SerializationOptions {
|
|
|
217
230
|
|
|
218
231
|
/** Inject Cascais design token CSS variables into `<defs><style>`. Default: true. */
|
|
219
232
|
readonly embedStyles?: boolean;
|
|
233
|
+
|
|
234
|
+
/** Optional diagram-level routing mode to serialize onto root `<svg>` tag. */
|
|
235
|
+
readonly routingMode?: AimRoutingMode;
|
|
220
236
|
}
|