@principal-ai/principal-view-react 0.16.56 → 0.16.58

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 (109) hide show
  1. package/dist/graphify/consolidated.d.ts +16 -1
  2. package/dist/graphify/consolidated.d.ts.map +1 -1
  3. package/dist/graphify/index.d.ts +1 -1
  4. package/dist/graphify/index.d.ts.map +1 -1
  5. package/dist/graphify/index.js.map +1 -1
  6. package/dist/graphify/kind.d.ts +1 -1
  7. package/dist/graphify/kind.d.ts.map +1 -1
  8. package/dist/graphify/kind.js +7 -1
  9. package/dist/graphify/kind.js.map +1 -1
  10. package/dist/index.d.ts +3 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +2 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/pierre/constructColors.d.ts +24 -0
  15. package/dist/pierre/constructColors.d.ts.map +1 -0
  16. package/dist/pierre/constructColors.js +91 -0
  17. package/dist/pierre/constructColors.js.map +1 -0
  18. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts +8 -0
  19. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -0
  20. package/dist/stories/Subsystem/ComponentGraph/fixtures.js +116 -0
  21. package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -0
  22. package/dist/subsystem/ComponentDeclaration.d.ts +2 -2
  23. package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
  24. package/dist/subsystem/ComponentDeclaration.js +12 -11
  25. package/dist/subsystem/ComponentDeclaration.js.map +1 -1
  26. package/dist/subsystem/SubsystemComponentGraph.d.ts +10 -1
  27. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  28. package/dist/subsystem/SubsystemComponentGraph.js +401 -40
  29. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  30. package/dist/subsystem/formatDeclaration.d.ts.map +1 -1
  31. package/dist/subsystem/formatDeclaration.js +49 -10
  32. package/dist/subsystem/formatDeclaration.js.map +1 -1
  33. package/dist/subsystem/model.d.ts +89 -12
  34. package/dist/subsystem/model.d.ts.map +1 -1
  35. package/dist/subsystem/model.js +55 -25
  36. package/dist/subsystem/model.js.map +1 -1
  37. package/dist/subsystem/nodes.d.ts +32 -1
  38. package/dist/subsystem/nodes.d.ts.map +1 -1
  39. package/dist/subsystem/nodes.js +122 -24
  40. package/dist/subsystem/nodes.js.map +1 -1
  41. package/dist/subsystem/tokenizeComponent.js +1 -1
  42. package/dist/subsystem/tokenizeComponent.js.map +1 -1
  43. package/dist/utils/elkLayout.d.ts.map +1 -1
  44. package/dist/utils/elkLayout.js +4 -3
  45. package/dist/utils/elkLayout.js.map +1 -1
  46. package/package.json +1 -1
  47. package/src/graphify/consolidated.ts +18 -1
  48. package/src/graphify/index.ts +1 -0
  49. package/src/graphify/kind.ts +7 -1
  50. package/src/index.ts +3 -2
  51. package/src/pierre/constructColors.test.ts +52 -0
  52. package/src/pierre/constructColors.ts +112 -0
  53. package/src/stories/{BoundaryNodes.stories.tsx → Audit/BoundaryNodes.stories.tsx} +1 -1
  54. package/src/stories/{CanvasEdgeTypes.stories.tsx → Audit/CanvasEdgeTypes.stories.tsx} +1 -1
  55. package/src/stories/{CanvasNodeTypes.stories.tsx → Audit/CanvasNodeTypes.stories.tsx} +1 -1
  56. package/src/stories/{ColorPriority.stories.tsx → Audit/ColorPriority.stories.tsx} +1 -1
  57. package/src/stories/{DiamondBadges.stories.tsx → Audit/DiamondBadges.stories.tsx} +1 -1
  58. package/src/stories/{NodeDimensionsTesting.stories.tsx → Audit/NodeDimensionsTesting.stories.tsx} +2 -2
  59. package/src/stories/{NodeFieldsAudit.stories.tsx → Audit/NodeFieldsAudit.stories.tsx} +1 -1
  60. package/src/stories/{NodeFontSizeTesting.stories.tsx → Audit/NodeFontSizeTesting.stories.tsx} +2 -2
  61. package/src/stories/{NodeShapes.stories.tsx → Audit/NodeShapes.stories.tsx} +1 -1
  62. package/src/stories/{WideNodesTesting.stories.tsx → Audit/WideNodesTesting.stories.tsx} +2 -2
  63. package/src/stories/{dashboard → Components}/DashboardRenderer.stories.tsx +2 -2
  64. package/src/stories/{GraphRenderer.stories.tsx → Components/GraphRenderer.stories.tsx} +2 -2
  65. package/src/stories/{MultiCanvasRenderer.stories.tsx → Components/MultiCanvasRenderer.stories.tsx} +2 -2
  66. package/src/stories/{SequenceDiagram.stories.tsx → Components/SequenceDiagram.stories.tsx} +2 -2
  67. package/src/stories/{StateView.stories.tsx → Components/StateView.stories.tsx} +2 -2
  68. package/src/stories/{TraceViewer.stories.tsx → Components/TraceViewer.stories.tsx} +3 -3
  69. package/src/stories/{WorkflowSequenceDiagram.stories.tsx → Components/WorkflowSequenceDiagram.stories.tsx} +1 -1
  70. package/src/stories/{AnimationWorkshop.stories.tsx → Features/AnimationWorkshop.stories.tsx} +5 -5
  71. package/src/stories/{ChangeTypeVisual.stories.tsx → Features/ChangeTypeVisual.stories.tsx} +2 -2
  72. package/src/stories/{EventDrivenAnimations.stories.tsx → Features/EventDrivenAnimations.stories.tsx} +2 -2
  73. package/src/stories/{MultiConfig.stories.tsx → Features/MultiConfig.stories.tsx} +2 -2
  74. package/src/stories/{ElkEdgeRouting.stories.tsx → Layout/ElkEdgeRouting.stories.tsx} +2 -2
  75. package/src/stories/{GraphOrientation.stories.tsx → Layout/GraphOrientation.stories.tsx} +3 -3
  76. package/src/stories/{MultiDirectionalConnections.stories.tsx → Layout/MultiDirectionalConnections.stories.tsx} +2 -2
  77. package/src/stories/{NodeDefinitionComparison.stories.tsx → Nodes/NodeDefinitionComparison.stories.tsx} +2 -2
  78. package/src/stories/{NodeNameFallbacks.stories.tsx → Nodes/NodeNameFallbacks.stories.tsx} +2 -2
  79. package/src/stories/{NumericColors.stories.tsx → Nodes/NumericColors.stories.tsx} +2 -2
  80. package/src/stories/{EventNameDisplay.stories.tsx → OTEL/EventNameDisplay.stories.tsx} +2 -2
  81. package/src/stories/{EventNameFallback.stories.tsx → OTEL/EventNameFallback.stories.tsx} +2 -2
  82. package/src/stories/{OtelComponents.stories.tsx → OTEL/OtelComponents.stories.tsx} +3 -3
  83. package/src/stories/{OtelNodeTypesPrototype.stories.tsx → OTEL/OtelNodeTypesPrototype.stories.tsx} +2 -2
  84. package/src/stories/{RawEventCards.stories.tsx → OTEL/RawEventCards.stories.tsx} +3 -3
  85. package/src/stories/{SpanBadges.stories.tsx → OTEL/SpanBadges.stories.tsx} +1 -1
  86. package/src/stories/{ValidationEventsCanvas.stories.tsx → OTEL/ValidationEventsCanvas.stories.tsx} +3 -3
  87. package/src/stories/{BacklogMdCanvases.stories.tsx → RealWorld/BacklogMdCanvases.stories.tsx} +2 -2
  88. package/src/stories/{FileCitySequence.stories.tsx → RealWorld/FileCitySequence.stories.tsx} +4 -4
  89. package/src/stories/{RealCanvasFiles.stories.tsx → RealWorld/RealCanvasFiles.stories.tsx} +7 -7
  90. package/src/stories/{ComponentDeclarationAudit.stories.tsx → Subsystem/ComponentDeclarationAudit.stories.tsx} +19 -20
  91. package/src/stories/Subsystem/ComponentGraph/Appearance.stories.tsx +365 -0
  92. package/src/stories/Subsystem/ComponentGraph/Basics.stories.tsx +137 -0
  93. package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +238 -0
  94. package/src/stories/Subsystem/ComponentGraph/DetailPanel.stories.tsx +315 -0
  95. package/src/stories/Subsystem/ComponentGraph/Flows.stories.tsx +181 -0
  96. package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +959 -0
  97. package/src/stories/Subsystem/ComponentGraph/Spotlights.stories.tsx +646 -0
  98. package/src/stories/Subsystem/ComponentGraph/fixtures.ts +129 -0
  99. package/src/subsystem/ComponentDeclaration.tsx +13 -13
  100. package/src/subsystem/SubsystemComponentGraph.tsx +558 -44
  101. package/src/subsystem/formatDeclaration.ts +57 -11
  102. package/src/subsystem/model.test.ts +38 -6
  103. package/src/subsystem/model.ts +145 -32
  104. package/src/subsystem/nodes.test.ts +56 -0
  105. package/src/subsystem/nodes.tsx +161 -31
  106. package/src/subsystem/tokenizeComponent.ts +1 -1
  107. package/src/utils/elkLayout.ts +4 -3
  108. package/src/stories/SubsystemComponentGraph.stories.tsx +0 -1225
  109. /package/src/stories/{dashboard/sample-dashboards → data}/activity-feed-analytics.dashboard.json +0 -0
@@ -16,18 +16,26 @@ import {
16
16
  } from '@xyflow/react';
17
17
  import { useTheme } from '@principal-ade/industry-theme';
18
18
  import {
19
- KIND_COLOR,
20
19
  MECHANISM_COLOR,
20
+ MECHANISM_STYLE,
21
+ ROLE_COLOR,
22
+ ROLE_LABEL,
21
23
  deriveNameFromSymbol,
22
24
  type SubsystemGraphNode,
23
25
  type SubsystemGraphEdge,
24
26
  } from './model';
27
+ import { constructColorsFromPierreTheme } from '../pierre/constructColors';
28
+ import { resolvePierreSyntaxThemeName } from '../pierre/pierreSyntaxTheme';
25
29
 
26
- export const KIND_LABEL: Record<string, string> = {
30
+ export const CONSTRUCT_LABEL: Record<string, string> = {
27
31
  class: 'class',
28
32
  function: 'function',
29
- type: 'type',
33
+ method: 'method',
34
+ interface: 'interface',
35
+ type_alias: 'type alias',
36
+ enum: 'enum',
30
37
  module: 'module',
38
+ store: 'store',
31
39
  external: 'external',
32
40
  };
33
41
 
@@ -48,6 +56,9 @@ function breakWords(s: string): string {
48
56
  export interface SubsystemGraphCallbacks {
49
57
  /** Click a component — open its file/entry point. */
50
58
  onSelect?: (componentId: string) => void;
59
+ /** Click the filename badge — open that component's file in the drawer
60
+ * (same path as clicking the file link in the declaration panel). */
61
+ onOpenFile?: (componentId: string) => void;
51
62
  /** Click an edge (or its label) — select the relationship. */
52
63
  onEdgeSelect?: (edgeId: string) => void;
53
64
  /** Hover a component (null on leave) — associates it with the file tree. */
@@ -60,15 +71,19 @@ export interface SubsystemGraphCallbacks {
60
71
  export const SUBSYSTEM_CALLBACKS: SubsystemGraphCallbacks = {};
61
72
 
62
73
  export function SubsystemComponentNode(props: NodeProps<SubsystemGraphNode>) {
63
- const { theme } = useTheme();
74
+ const { theme, mode } = useTheme();
64
75
  const { data, selected, width: nodeWidth, height: nodeHeight } = props;
65
76
  const c = data.component;
66
- const color = KIND_COLOR[c.kind] ?? '#888';
77
+ // Construct owns node color, derived from the active Pierre syntax theme —
78
+ // the same palette the declaration panel and file drawer render with. Role
79
+ // shows as the hover badge, not as color — for now; a role glyph/accent may
80
+ // come later.
81
+ const color = constructColorsFromPierreTheme(resolvePierreSyntaxThemeName(mode))[c.construct];
67
82
  const [hover, setHover] = useState(false);
68
83
  const configuredMax = SUBSYSTEM_CALLBACKS.maxNodeWidth;
69
84
  const maxWidth = configuredMax ?? 300;
70
85
  // `symbol` is the source of truth; `name` is derived from it consistently.
71
- const displayName = deriveNameFromSymbol(c.symbol, c.kind, c.name, c.file);
86
+ const displayName = deriveNameFromSymbol(c.symbol, c.construct, c.name, c.file);
72
87
  // Set while a file is open in the drawer: true → spotlight, false → dim,
73
88
  // absent (no file open) → neutral.
74
89
  const fileMatch = data.fileMatch as boolean | undefined;
@@ -111,42 +126,97 @@ export function SubsystemComponentNode(props: NodeProps<SubsystemGraphNode>) {
111
126
  boxShadow: fileMatch
112
127
  ? `0 1px 4px rgba(0,0,0,0.25), 0 0 12px ${theme.colors.primary}55`
113
128
  : '0 1px 4px rgba(0,0,0,0.25)',
114
- opacity: fileMatch === false ? 0.18 : 1,
129
+ opacity: fileMatch === false || data.dimmed === true ? 0.18 : 1,
115
130
  transition: 'opacity 150ms ease',
116
131
  cursor: 'pointer',
117
132
  fontFamily: theme.fonts.body,
118
133
  }}
119
134
  >
120
- {/* Kind + package tooltip, shown at the top on hover */}
121
- {hover && (
135
+ {/* Construct badge a small tab riding the top-right border, in the
136
+ construct color. Persistent (no hover needed); pointer-events none so
137
+ clicks pass through to the node. */}
138
+ <div
139
+ style={{
140
+ position: 'absolute',
141
+ top: -9,
142
+ left: 10,
143
+ zIndex: 1,
144
+ fontFamily: theme.fonts.monospace,
145
+ fontSize: theme.fontSizes[0] * 1.1,
146
+ letterSpacing: 0.5,
147
+ textTransform: 'uppercase',
148
+ lineHeight: '17px',
149
+ color,
150
+ background: theme.colors.backgroundSecondary ?? theme.colors.background,
151
+ border: `1px solid ${color}`,
152
+ borderRadius: 4,
153
+ padding: '0 5px',
154
+ }}
155
+ >
156
+ {CONSTRUCT_LABEL[c.construct] ?? c.construct}
157
+ </div>
158
+
159
+ {/* Role badge — top-right, only when the node carries a topology role.
160
+ Role color (entry orange / service blue) on the badge; the node
161
+ border stays construct-colored. */}
162
+ {c.role != null && (
122
163
  <div
123
164
  style={{
124
165
  position: 'absolute',
125
- top: -26,
126
- left: 0,
127
- zIndex: 1000,
128
- display: 'flex',
129
- alignItems: 'center',
130
- gap: 6,
131
- whiteSpace: 'nowrap',
132
- fontSize: theme.fontSizes[0] * 0.8,
166
+ top: -9,
167
+ right: 10,
168
+ zIndex: 1,
133
169
  fontFamily: theme.fonts.monospace,
170
+ fontSize: theme.fontSizes[0] * 1.1,
134
171
  letterSpacing: 0.5,
135
- color,
136
- background: theme.colors.background,
137
- border: `1px solid ${theme.colors.border}`,
172
+ textTransform: 'uppercase',
173
+ lineHeight: '17px',
174
+ color: ROLE_COLOR[c.role],
175
+ background: theme.colors.backgroundSecondary ?? theme.colors.background,
176
+ border: `1px solid ${ROLE_COLOR[c.role]}`,
138
177
  borderRadius: 4,
139
- padding: '1px 6px',
178
+ padding: '0 5px',
140
179
  }}
141
180
  >
142
- <span style={{ textTransform: 'uppercase' }}>
143
- {KIND_LABEL[c.kind] ?? c.kind}
144
- </span>
145
- {c.file ? ` · ${c.file.split('/').pop()}` : ''}
146
- {c.capture && c.capture !== 'edited' ? ` · ${c.capture}` : ''}
181
+ {ROLE_LABEL[c.role]}
147
182
  </div>
148
183
  )}
149
184
 
185
+ {/* Filename badge — a strip across the bottom border, shown on hover
186
+ and pinned while this node's file is open in the drawer (fileMatch).
187
+ Mirrors the construct/role tab badges; hidden for fileless nodes. */}
188
+ {(hover || fileMatch === true) && c.file ? (
189
+ <div
190
+ style={{
191
+ position: 'absolute',
192
+ bottom: -13,
193
+ left: '50%',
194
+ transform: 'translateX(-50%)',
195
+ zIndex: 1,
196
+ fontFamily: theme.fonts.monospace,
197
+ fontSize: theme.fontSizes[0] * 1.1,
198
+ letterSpacing: 0.5,
199
+ lineHeight: '17px',
200
+ whiteSpace: 'nowrap',
201
+ // filename stays neutral — the construct color belongs to the
202
+ // borders and badges, not the file name
203
+ color: theme.colors.text ?? theme.colors.textSecondary,
204
+ background: theme.colors.backgroundSecondary ?? theme.colors.background,
205
+ border: `1px solid ${color}`,
206
+ borderRadius: 4,
207
+ padding: '0 5px',
208
+ cursor: 'pointer',
209
+ }}
210
+ onClick={(e) => {
211
+ // Open the file directly; don't also toggle node selection.
212
+ e.stopPropagation();
213
+ SUBSYSTEM_CALLBACKS.onOpenFile?.(c.id);
214
+ }}
215
+ >
216
+ {c.file.split('/').pop()}
217
+ </div>
218
+ ) : null}
219
+
150
220
  {/* Purpose is no longer shown as a hover tooltip below the node — it
151
221
  lives in the declaration panel and the graphify detail payload. */}
152
222
 
@@ -168,7 +238,11 @@ export function SubsystemComponentNode(props: NodeProps<SubsystemGraphNode>) {
168
238
  {breakWords(displayName)}
169
239
  </div>
170
240
 
171
- {c.symbol && c.symbol !== displayName && (
241
+ {/* Hide the identity line when the symbol is just the title without its
242
+ decoration (`()` or ` {}`) — only show it when it adds information
243
+ (e.g. the dotted host on methods, or a different code identity). */}
244
+ {c.symbol &&
245
+ c.symbol !== displayName.replace(/ ?\{\}$/, '').replace(/\(\)$/, '') && (
172
246
  <div
173
247
  style={{
174
248
  fontSize: theme.fontSizes[0] * 0.82,
@@ -193,6 +267,58 @@ export function SubsystemComponentNode(props: NodeProps<SubsystemGraphNode>) {
193
267
  );
194
268
  }
195
269
 
270
+ /** `#rrggbb` + alpha → `#rrggbbaa`. Used to dim a stroke/marker by color so
271
+ * each opacity gets its own SVG marker id — path `opacity` leaks across every
272
+ * edge that shares a `url(#marker)` (the focused edge's arrowhead dims). */
273
+ export function hexWithAlpha(hex: string, alpha: number): string {
274
+ const raw = hex.replace('#', '');
275
+ const full = raw.length === 3 ? [...raw].map((c) => c + c).join('') : raw;
276
+ if (!/^[0-9a-fA-F]{6}$/.test(full)) return hex;
277
+ const a = Math.round(Math.min(1, Math.max(0, alpha)) * 255)
278
+ .toString(16)
279
+ .padStart(2, '0');
280
+ return `#${full}${a}`;
281
+ }
282
+
283
+ /**
284
+ * File-open spotlight flag for a node. `true` = lives in the open file,
285
+ * `false` = dim, `undefined` = render neutrally.
286
+ *
287
+ * Focused-edge endpoints stay neutral when they don't live in the open file
288
+ * (the target of a focused edge must not dim with the rest).
289
+ */
290
+ export function fileMatchForNode(
291
+ nodeFile: string | undefined,
292
+ openFile: string | null,
293
+ isFocusEndpoint: boolean,
294
+ ): boolean | undefined {
295
+ if (!openFile) return undefined;
296
+ if (nodeFile === openFile) return true;
297
+ if (isFocusEndpoint) return undefined;
298
+ return false;
299
+ }
300
+
301
+ /**
302
+ * Hide / dim a node or edge while flows are open.
303
+ * - not in any opened flow → hidden
304
+ * - in an opened flow, but not the selected flow/step → dimmed
305
+ * - in the selected flow or step (or opened with nothing selected) → full
306
+ */
307
+ export function flowElementVisibility(opts: {
308
+ inOpened: boolean;
309
+ inSelected: boolean;
310
+ anyOpened: boolean;
311
+ anySelected: boolean;
312
+ }): { hidden: boolean; dimmed: boolean } {
313
+ const { inOpened, inSelected, anyOpened, anySelected } = opts;
314
+ if (!anyOpened && !anySelected) return { hidden: false, dimmed: false };
315
+ if (!inOpened && !inSelected) return { hidden: true, dimmed: false };
316
+ if (anySelected && !inSelected) return { hidden: false, dimmed: true };
317
+ return { hidden: false, dimmed: false };
318
+ }
319
+
320
+ export const EDGE_DIM_ALPHA = 0.15;
321
+
196
322
  /** Subsystem edge — SVG path only. The mechanism label is rendered as an
197
323
  * absolutely-positioned HTML overlay OUTSIDE the ReactFlow tree (by the
198
324
  * parent Inner component) so it sits above the pane and receives pointer
@@ -204,9 +330,14 @@ export function SubsystemEdge({
204
330
  const path = data?.elkPath ?? '';
205
331
  const mechanism = data?.mechanism ?? 'imports';
206
332
  const color = MECHANISM_COLOR[mechanism] ?? '#888';
207
- const isDashed = mechanism === 'registers-into';
333
+ // Dash style comes from the mechanism table (dashed = inverted-control or
334
+ // observational relationships: hierarchy, registration, watches).
335
+ const isDashed = MECHANISM_STYLE[mechanism] === 'dashed';
208
336
  const dimmed = data?.dimmed === true;
209
- const opacity = dimmed ? 0.15 : 1;
337
+ // Dim the stroke by color, never via path `opacity`. SVG markers are shared
338
+ // by id; opacity on the referencing path paints every arrowhead that uses
339
+ // the same marker — including the focused edge's target.
340
+ const stroke = dimmed ? hexWithAlpha(color, EDGE_DIM_ALPHA) : color;
210
341
 
211
342
  return (
212
343
  <>
@@ -225,10 +356,9 @@ export function SubsystemEdge({
225
356
  <path
226
357
  d={path}
227
358
  fill="none"
228
- stroke={color}
359
+ stroke={stroke}
229
360
  strokeWidth={1.5}
230
361
  strokeDasharray={isDashed ? '6 4' : undefined}
231
- opacity={opacity}
232
362
  markerEnd={markerEnd}
233
363
  style={{ pointerEvents: 'none' }}
234
364
  />
@@ -50,7 +50,7 @@ export async function tokenizeComponent(
50
50
  if (component.tokens) return component.tokens;
51
51
 
52
52
  // External kind — not valid TypeScript, bypass Prettier.
53
- const kind = component.detail?.kind ?? component.kind;
53
+ const kind = component.detail?.kind ?? component.construct;
54
54
  if (kind === 'external') {
55
55
  const label = component.detail?.kind === 'external' ? component.detail.label : component.name;
56
56
  const escaped = label.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
@@ -529,9 +529,10 @@ export async function computeElkLayout(
529
529
  // and apply the same coordinate offset.
530
530
  if (edge.labels && edge.labels.length > 0) {
531
531
  const elkLabel = edge.labels[0];
532
- // Raw ELK label position no conversion.
533
- let lx = elkLabel.x ?? 0;
534
- let ly = elkLabel.y ?? 0;
532
+ // ELK reports the label's top-left; convert to center so screen-space
533
+ // overlays can anchor with translate(-50%, -50%) at any zoom.
534
+ let lx = (elkLabel.x ?? 0) + (elkLabel.width ?? 0) / 2;
535
+ let ly = (elkLabel.y ?? 0) + (elkLabel.height ?? 0) / 2;
535
536
  if (preserveNodePositions && sourceOriginal && sourceElk && targetOriginal && targetElk) {
536
537
  const sourceOffset = {
537
538
  x: sourceOriginal.x - sourceElk.x,