@eventcatalog/visualiser 3.19.0 → 3.20.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/index.js CHANGED
@@ -104,7 +104,7 @@ module.exports = __toCommonJS(index_exports);
104
104
  var import_react79 = require("react");
105
105
  var import_react_dom = require("react-dom");
106
106
  var import_react80 = require("@xyflow/react");
107
- var import_lucide_react31 = require("lucide-react");
107
+ var import_lucide_react32 = require("lucide-react");
108
108
  var DropdownMenu2 = __toESM(require("@radix-ui/react-dropdown-menu"));
109
109
  var import_html_to_image = require("html-to-image");
110
110
 
@@ -5088,138 +5088,291 @@ var GroupNode_default = (0, import_react45.memo)(function GroupNode({ data }) {
5088
5088
  });
5089
5089
 
5090
5090
  // src/nodes/Custom.tsx
5091
- var import_react46 = require("@xyflow/react");
5091
+ var import_react46 = require("react");
5092
+ var import_react47 = require("@xyflow/react");
5092
5093
  var Icons = __toESM(require("@heroicons/react/24/solid"));
5093
- var import_react47 = require("react");
5094
5094
  var ContextMenu4 = __toESM(require("@radix-ui/react-context-menu"));
5095
- var Tooltip = __toESM(require("@radix-ui/react-tooltip"));
5096
5095
  var import_jsx_runtime24 = require("react/jsx-runtime");
5096
+ var PALETTES = {
5097
+ blue: {
5098
+ border: "border-blue-500",
5099
+ badge: "bg-blue-500",
5100
+ ring: "ring-2 ring-blue-400/60 ring-offset-2",
5101
+ shadow: "rgba(59, 130, 246, 0.15)",
5102
+ glow: "linear-gradient(135deg, #3b82f6, #2563eb)",
5103
+ pillBorder: "#3b82f6"
5104
+ },
5105
+ teal: {
5106
+ border: "border-teal-500",
5107
+ badge: "bg-teal-500",
5108
+ ring: "ring-2 ring-teal-400/60 ring-offset-2",
5109
+ shadow: "rgba(20, 184, 166, 0.15)",
5110
+ glow: "linear-gradient(135deg, #14b8a6, #0f766e)",
5111
+ pillBorder: "#14b8a6"
5112
+ },
5113
+ red: {
5114
+ border: "border-red-500",
5115
+ badge: "bg-red-500",
5116
+ ring: "ring-2 ring-red-400/60 ring-offset-2",
5117
+ shadow: "rgba(239, 68, 68, 0.15)",
5118
+ glow: "linear-gradient(135deg, #ef4444, #b91c1c)",
5119
+ pillBorder: "#ef4444"
5120
+ },
5121
+ green: {
5122
+ border: "border-green-500",
5123
+ badge: "bg-green-500",
5124
+ ring: "ring-2 ring-green-400/60 ring-offset-2",
5125
+ shadow: "rgba(34, 197, 94, 0.15)",
5126
+ glow: "linear-gradient(135deg, #22c55e, #15803d)",
5127
+ pillBorder: "#22c55e"
5128
+ },
5129
+ purple: {
5130
+ border: "border-purple-500",
5131
+ badge: "bg-purple-500",
5132
+ ring: "ring-2 ring-purple-400/60 ring-offset-2",
5133
+ shadow: "rgba(168, 85, 247, 0.15)",
5134
+ glow: "linear-gradient(135deg, #a855f7, #7e22ce)",
5135
+ pillBorder: "#a855f7"
5136
+ },
5137
+ orange: {
5138
+ border: "border-orange-500",
5139
+ badge: "bg-orange-500",
5140
+ ring: "ring-2 ring-orange-400/60 ring-offset-2",
5141
+ shadow: "rgba(251, 146, 60, 0.15)",
5142
+ glow: "linear-gradient(135deg, #fb923c, #ea580c)",
5143
+ pillBorder: "#fb923c"
5144
+ },
5145
+ pink: {
5146
+ border: "border-pink-500",
5147
+ badge: "bg-pink-500",
5148
+ ring: "ring-2 ring-pink-400/60 ring-offset-2",
5149
+ shadow: "rgba(236, 72, 153, 0.15)",
5150
+ glow: "linear-gradient(135deg, #ec4899, #be185d)",
5151
+ pillBorder: "#ec4899"
5152
+ },
5153
+ yellow: {
5154
+ border: "border-yellow-500",
5155
+ badge: "bg-yellow-500",
5156
+ ring: "ring-2 ring-yellow-400/60 ring-offset-2",
5157
+ shadow: "rgba(234, 179, 8, 0.15)",
5158
+ glow: "linear-gradient(135deg, #eab308, #a16207)",
5159
+ pillBorder: "#eab308"
5160
+ },
5161
+ gray: {
5162
+ border: "border-gray-500",
5163
+ badge: "bg-gray-500",
5164
+ ring: "ring-2 ring-gray-400/60 ring-offset-2",
5165
+ shadow: "rgba(107, 114, 128, 0.15)",
5166
+ glow: "linear-gradient(135deg, #6b7280, #374151)",
5167
+ pillBorder: "#6b7280"
5168
+ },
5169
+ indigo: {
5170
+ border: "border-indigo-500",
5171
+ badge: "bg-indigo-500",
5172
+ ring: "ring-2 ring-indigo-400/60 ring-offset-2",
5173
+ shadow: "rgba(99, 102, 241, 0.15)",
5174
+ glow: "linear-gradient(135deg, #6366f1, #4338ca)",
5175
+ pillBorder: "#6366f1"
5176
+ },
5177
+ cyan: {
5178
+ border: "border-cyan-500",
5179
+ badge: "bg-cyan-500",
5180
+ ring: "ring-2 ring-cyan-400/60 ring-offset-2",
5181
+ shadow: "rgba(6, 182, 212, 0.15)",
5182
+ glow: "linear-gradient(135deg, #06b6d4, #0e7490)",
5183
+ pillBorder: "#06b6d4"
5184
+ },
5185
+ slate: {
5186
+ border: "border-slate-500",
5187
+ badge: "bg-slate-500",
5188
+ ring: "ring-2 ring-slate-400/60 ring-offset-2",
5189
+ shadow: "rgba(100, 116, 139, 0.15)",
5190
+ glow: "linear-gradient(135deg, #64748b, #334155)",
5191
+ pillBorder: "#64748b"
5192
+ },
5193
+ amber: {
5194
+ border: "border-amber-500",
5195
+ badge: "bg-amber-500",
5196
+ ring: "ring-2 ring-amber-400/60 ring-offset-2",
5197
+ shadow: "rgba(245, 158, 11, 0.15)",
5198
+ glow: "linear-gradient(135deg, #f59e0b, #b45309)",
5199
+ pillBorder: "#f59e0b"
5200
+ },
5201
+ emerald: {
5202
+ border: "border-emerald-500",
5203
+ badge: "bg-emerald-500",
5204
+ ring: "ring-2 ring-emerald-400/60 ring-offset-2",
5205
+ shadow: "rgba(16, 185, 129, 0.15)",
5206
+ glow: "linear-gradient(135deg, #10b981, #047857)",
5207
+ pillBorder: "#10b981"
5208
+ },
5209
+ violet: {
5210
+ border: "border-violet-500",
5211
+ badge: "bg-violet-500",
5212
+ ring: "ring-2 ring-violet-400/60 ring-offset-2",
5213
+ shadow: "rgba(139, 92, 246, 0.15)",
5214
+ glow: "linear-gradient(135deg, #8b5cf6, #6d28d9)",
5215
+ pillBorder: "#8b5cf6"
5216
+ },
5217
+ rose: {
5218
+ border: "border-rose-500",
5219
+ badge: "bg-rose-500",
5220
+ ring: "ring-2 ring-rose-400/60 ring-offset-2",
5221
+ shadow: "rgba(244, 63, 94, 0.15)",
5222
+ glow: "linear-gradient(135deg, #f43f5e, #be123c)",
5223
+ pillBorder: "#f43f5e"
5224
+ }
5225
+ };
5097
5226
  function classNames16(...classes) {
5098
5227
  return classes.filter(Boolean).join(" ");
5099
5228
  }
5100
- var Custom_default = (0, import_react47.memo)(function UserNode2({
5101
- data,
5102
- sourcePosition,
5103
- targetPosition
5229
+ function getPalette(color) {
5230
+ return PALETTES[color || "blue"] ?? PALETTES.blue;
5231
+ }
5232
+ function GlowHandle14({
5233
+ side,
5234
+ gradient
5104
5235
  }) {
5105
- const { mode, step, custom: customProps } = data;
5236
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
5237
+ "div",
5238
+ {
5239
+ style: {
5240
+ position: "absolute",
5241
+ top: "50%",
5242
+ [side]: -6,
5243
+ transform: "translateY(-50%)",
5244
+ width: 12,
5245
+ height: 12,
5246
+ borderRadius: "50%",
5247
+ background: gradient,
5248
+ border: "2px solid rgb(var(--ec-page-bg))",
5249
+ zIndex: 20,
5250
+ animation: "ec-handle-pulse 2s ease-in-out infinite",
5251
+ pointerEvents: "none"
5252
+ }
5253
+ }
5254
+ );
5255
+ }
5256
+ var Custom_default = (0, import_react46.memo)(function CustomNode({ data, selected }) {
5257
+ const { mode, custom: customProps, step } = data;
5106
5258
  const {
5107
5259
  color = "blue",
5108
- title = "Custom",
5109
- icon = "UserIcon",
5110
- type = "custom",
5111
- summary = "",
5112
- url: _url = "",
5260
+ title = step?.title || "Custom",
5261
+ icon = "CubeIcon",
5262
+ type = "Custom",
5263
+ summary = step?.summary || "",
5264
+ url,
5113
5265
  properties = EMPTY_OBJECT,
5114
5266
  menu = EMPTY_ARRAY,
5115
5267
  height = 5
5116
5268
  } = customProps;
5117
- const IconComponent = (0, import_react47.useMemo)(() => Icons[icon], [icon]);
5118
- const { actor: { name: _name } = {} } = step;
5119
- const isLongType = type && type.length > 10;
5120
- const displayType = isLongType ? `${type.substring(0, 10)}...` : type;
5269
+ const palette = getPalette(color);
5270
+ const IconComponent = (0, import_react46.useMemo)(() => Icons[icon] || Icons.CubeIcon, [icon]);
5121
5271
  const portalContainer = usePortalContainer();
5272
+ const targetConnections = (0, import_react47.useNodeConnections)({ handleType: "target" });
5273
+ const sourceConnections = (0, import_react47.useNodeConnections)({ handleType: "source" });
5274
+ const propertyEntries = Object.entries(properties);
5275
+ const contextMenuItems = url ? [{ label: `Open ${type.toLowerCase()}`, url }, ...menu] : menu;
5122
5276
  return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(ContextMenu4.Root, { children: [
5123
5277
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ContextMenu4.Trigger, { children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
5124
5278
  "div",
5125
5279
  {
5126
5280
  className: classNames16(
5127
- `rounded-md border flex justify-start bg-[rgb(var(--ec-card-bg))] text-[rgb(var(--ec-page-text))] border-${color}-400`
5281
+ "relative min-w-48 max-w-60 rounded-xl border-2 overflow-visible",
5282
+ selected ? palette.ring : "",
5283
+ palette.border
5128
5284
  ),
5129
5285
  style: {
5130
- minHeight: mode === "full" ? `${height}em` : "2em",
5131
- ...NODE_WIDTH_STYLE
5286
+ background: "var(--ec-custom-node-bg, rgb(var(--ec-card-bg)))",
5287
+ boxShadow: `0 2px 12px ${palette.shadow}`,
5288
+ minHeight: mode === "full" ? `${height}em` : void 0
5132
5289
  },
5133
5290
  children: [
5134
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
5135
- "div",
5291
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
5292
+ import_react47.Handle,
5293
+ {
5294
+ type: "target",
5295
+ position: import_react47.Position.Left,
5296
+ style: HIDDEN_HANDLE_STYLE
5297
+ }
5298
+ ),
5299
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
5300
+ import_react47.Handle,
5301
+ {
5302
+ type: "source",
5303
+ position: import_react47.Position.Right,
5304
+ style: HIDDEN_HANDLE_STYLE
5305
+ }
5306
+ ),
5307
+ targetConnections.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(GlowHandle14, { side: "left", gradient: palette.glow }),
5308
+ sourceConnections.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(GlowHandle14, { side: "right", gradient: palette.glow }),
5309
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "absolute -top-3 left-2.5 z-10", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
5310
+ "span",
5136
5311
  {
5137
5312
  className: classNames16(
5138
- `bg-gradient-to-b from-${color}-400 to-${color}-600 relative flex flex-col items-center w-5 justify-between rounded-l-sm text-orange-100-500`,
5139
- `border-r-[1px] border-${color}`
5313
+ "inline-flex items-center gap-1 text-[7px] font-bold uppercase tracking-widest text-white px-1.5 py-0.5 rounded shadow-sm",
5314
+ palette.badge
5140
5315
  ),
5141
5316
  children: [
5142
- IconComponent && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(IconComponent, { className: "w-4 h-4 opacity-90 text-white mt-1" }),
5143
- mode === "full" && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Tooltip.Provider, { children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(Tooltip.Root, { children: [
5144
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Tooltip.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
5145
- "span",
5146
- {
5147
- className: "text-center text-[9px] text-white font-bold uppercase mb-4",
5148
- style: ROTATED_LABEL_STYLE,
5149
- children: displayType
5150
- }
5151
- ) }),
5152
- isLongType && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Tooltip.Portal, { container: portalContainer, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
5153
- Tooltip.Content,
5154
- {
5155
- className: "bg-slate-800 text-white rounded px-2 py-1 text-xs shadow-md z-50",
5156
- side: "right",
5157
- sideOffset: 5,
5158
- children: [
5159
- type,
5160
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Tooltip.Arrow, { className: "fill-slate-800" })
5161
- ]
5162
- }
5163
- ) })
5164
- ] }) })
5317
+ IconComponent && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(IconComponent, { className: "w-2.5 h-2.5", "aria-hidden": true }),
5318
+ type
5165
5319
  ]
5166
5320
  }
5167
- ),
5168
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "p-1 flex-1", children: [
5169
- targetPosition && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react46.Handle, { type: "target", position: targetPosition }),
5170
- sourcePosition && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react46.Handle, { type: "source", position: sourcePosition }),
5171
- (!summary || mode !== "full") && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "h-full ", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "text-sm font-bold block pb-0.5 w-full", children: title }) }),
5172
- summary && mode === "full" && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { children: [
5173
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
5321
+ ) }),
5322
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "px-3 pt-3.5 pb-2.5", children: [
5323
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "text-[13px] font-semibold leading-snug text-[rgb(var(--ec-page-text))] truncate", children: title }) }),
5324
+ mode === "full" && summary && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
5325
+ "div",
5326
+ {
5327
+ className: "mt-1.5 text-[9px] text-[rgb(var(--ec-page-text-muted))] leading-relaxed overflow-hidden",
5328
+ style: LINE_CLAMP_STYLE,
5329
+ title: summary,
5330
+ children: summary
5331
+ }
5332
+ ),
5333
+ mode === "full" && propertyEntries.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "mt-2 pt-1.5 border-t border-[rgb(var(--ec-page-border))] grid grid-cols-2 gap-x-2 gap-y-1", children: propertyEntries.map(([key, value]) => /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "min-w-0", children: [
5334
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "text-[7px] font-bold uppercase tracking-wide text-[rgb(var(--ec-page-text-muted))] truncate", children: key }),
5335
+ typeof value === "string" && value.startsWith("http") ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
5336
+ "a",
5337
+ {
5338
+ href: value,
5339
+ target: "_blank",
5340
+ rel: "noopener noreferrer",
5341
+ className: "block text-[8px] text-blue-500 underline truncate",
5342
+ title: value,
5343
+ children: value
5344
+ }
5345
+ ) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
5174
5346
  "div",
5175
5347
  {
5176
- className: classNames16(
5177
- mode === "full" ? `border-b border-[rgb(var(--ec-page-border))]` : ""
5178
- ),
5179
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "text-xs font-bold block pb-0.5", children: title })
5348
+ className: "text-[8px] text-[rgb(var(--ec-page-text))] truncate",
5349
+ title: String(value),
5350
+ children: value
5180
5351
  }
5181
- ),
5182
- mode === "full" && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "divide-y divide-[rgb(var(--ec-page-border))] ", children: [
5183
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "leading-3 py-1", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "text-[8px] font-light", children: summary }) }),
5184
- properties && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "grid grid-cols-2 gap-x-4 py-1", children: Object.entries(properties).map(([key, value]) => /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
5185
- "span",
5186
- {
5187
- className: "text-xs",
5188
- style: TINY_FONT_STYLE,
5189
- children: [
5190
- key,
5191
- ":",
5192
- " ",
5193
- typeof value === "string" && value.startsWith("http") ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
5194
- "a",
5195
- {
5196
- href: value,
5197
- target: "_blank",
5198
- rel: "noopener noreferrer",
5199
- className: "text-blue-500 underline",
5200
- children: value
5201
- }
5202
- ) : value
5203
- ]
5204
- },
5205
- key
5206
- )) })
5207
- ] })
5208
- ] })
5352
+ )
5353
+ ] }, key)) })
5209
5354
  ] })
5210
5355
  ]
5211
5356
  }
5212
5357
  ) }),
5213
- menu?.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ContextMenu4.Portal, { container: portalContainer, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ContextMenu4.Content, { className: "min-w-[220px] bg-[rgb(var(--ec-card-bg))] rounded-md p-1 shadow-md border border-[rgb(var(--ec-page-border))]", children: menu?.map((item) => {
5214
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
5215
- ContextMenu4.Item,
5216
- {
5217
- asChild: true,
5218
- className: "text-sm px-2 py-1.5 outline-none cursor-pointer hover:bg-orange-100 rounded-sm flex items-center",
5219
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("a", { href: item.url, children: item.label })
5220
- }
5221
- );
5222
- }) }) })
5358
+ contextMenuItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ContextMenu4.Portal, { container: portalContainer, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ContextMenu4.Content, { className: "min-w-[220px] bg-[rgb(var(--ec-card-bg))] rounded-md p-1 shadow-md border border-[rgb(var(--ec-page-border))]", children: contextMenuItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
5359
+ ContextMenu4.Item,
5360
+ {
5361
+ asChild: true,
5362
+ className: "text-sm px-2 py-1.5 outline-none cursor-pointer hover:bg-orange-100 rounded-sm flex items-center",
5363
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
5364
+ "a",
5365
+ {
5366
+ href: item.url,
5367
+ target: "_blank",
5368
+ rel: "noopener noreferrer",
5369
+ className: "text-[rgb(var(--ec-page-text))] no-underline",
5370
+ children: item.label
5371
+ }
5372
+ )
5373
+ },
5374
+ `${item.label}-${item.url || ""}`
5375
+ )) }) })
5223
5376
  ] });
5224
5377
  });
5225
5378
 
@@ -5256,7 +5409,7 @@ var import_react50 = require("react");
5256
5409
  var import_lucide_react22 = require("lucide-react");
5257
5410
  var import_react51 = require("@xyflow/react");
5258
5411
  var import_jsx_runtime26 = require("react/jsx-runtime");
5259
- function GlowHandle14({ side }) {
5412
+ function GlowHandle15({ side }) {
5260
5413
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
5261
5414
  "div",
5262
5415
  {
@@ -5425,8 +5578,8 @@ function DefaultDataProduct(props) {
5425
5578
  }
5426
5579
  ),
5427
5580
  notes && notes.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(NotesIndicator, { notes, resourceName: name }),
5428
- targetConnections.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(GlowHandle14, { side: "left" }),
5429
- sourceConnections.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(GlowHandle14, { side: "right" }),
5581
+ targetConnections.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(GlowHandle15, { side: "left" }),
5582
+ sourceConnections.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(GlowHandle15, { side: "right" }),
5430
5583
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "absolute -top-2.5 left-2.5 flex items-center gap-1.5 z-10", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
5431
5584
  "span",
5432
5585
  {
@@ -6101,7 +6254,64 @@ var FlowEdge_default = (0, import_react59.memo)(function CustomEdge({
6101
6254
 
6102
6255
  // src/components/VisualiserSearch.tsx
6103
6256
  var import_react61 = require("react");
6257
+ var import_lucide_react25 = require("lucide-react");
6104
6258
  var import_jsx_runtime32 = require("react/jsx-runtime");
6259
+ var formatVersionedName = (name, version) => {
6260
+ if (version) {
6261
+ const nameWithoutVersion = name.replace(
6262
+ new RegExp(`-v?${version.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`),
6263
+ ""
6264
+ );
6265
+ return `${nameWithoutVersion} (v${version})`;
6266
+ }
6267
+ const versionMatch = name.match(
6268
+ /^(.+)-v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)$/
6269
+ );
6270
+ if (!versionMatch) return name;
6271
+ return `${versionMatch[1]} (v${versionMatch[2]})`;
6272
+ };
6273
+ var normalizeCollectionType = (type) => {
6274
+ const aliases = {
6275
+ event: "events",
6276
+ command: "commands",
6277
+ query: "queries",
6278
+ service: "services",
6279
+ domain: "domains",
6280
+ channel: "channels",
6281
+ entity: "entities"
6282
+ };
6283
+ return aliases[type] || type;
6284
+ };
6285
+ var splitVersionedName = (name, version) => {
6286
+ if (version) {
6287
+ return {
6288
+ id: name.replace(
6289
+ new RegExp(`-v?${version.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`),
6290
+ ""
6291
+ ),
6292
+ version
6293
+ };
6294
+ }
6295
+ const versionMatch = name.match(
6296
+ /^(.+)-v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)$/
6297
+ );
6298
+ if (!versionMatch) return { id: name, version: void 0 };
6299
+ return { id: versionMatch[1], version: versionMatch[2] };
6300
+ };
6301
+ var getResourceKey = ({
6302
+ type,
6303
+ id,
6304
+ name,
6305
+ version
6306
+ }) => {
6307
+ const parsed = splitVersionedName(id || name, version);
6308
+ return `${normalizeCollectionType(type)}:${parsed.id}:${parsed.version || ""}`.toLowerCase();
6309
+ };
6310
+ var getNodeResourceData = (data, key) => {
6311
+ const resource = data?.[key];
6312
+ if (!resource || typeof resource !== "object") return void 0;
6313
+ return "data" in resource && resource.data ? resource.data : resource;
6314
+ };
6105
6315
  var VisualiserSearch = (0, import_react61.memo)(
6106
6316
  (0, import_react61.forwardRef)(
6107
6317
  ({ nodes, onNodeSelect, onClear, onPaneClick: _onPaneClick }, ref) => {
@@ -6111,6 +6321,8 @@ var VisualiserSearch = (0, import_react61.memo)(
6111
6321
  const [selectedSuggestionIndex, setSelectedSuggestionIndex] = (0, import_react61.useState)(-1);
6112
6322
  const searchInputRef = (0, import_react61.useRef)(null);
6113
6323
  const containerRef = (0, import_react61.useRef)(null);
6324
+ const suggestionsListRef = (0, import_react61.useRef)(null);
6325
+ const suggestionItemRefs = (0, import_react61.useRef)([]);
6114
6326
  const hideSuggestions = (0, import_react61.useCallback)(() => {
6115
6327
  setShowSuggestions(false);
6116
6328
  setSelectedSuggestionIndex(-1);
@@ -6129,84 +6341,277 @@ var VisualiserSearch = (0, import_react61.memo)(
6129
6341
  if (node.type === "messageGroupExpanded") {
6130
6342
  return node.data?.groupName || node.id;
6131
6343
  }
6132
- const name = node.data?.message?.data?.name || node.data?.service?.data?.name || node.data?.domain?.data?.name || node.data?.entity?.data?.name || node.data?.name || node.id;
6133
- const version = node.data?.message?.data?.version || node.data?.service?.data?.version || node.data?.domain?.data?.version || node.data?.entity?.data?.version || node.data?.version;
6134
- return version ? `${name} (v${version})` : name;
6344
+ const message = getNodeResourceData(node.data, "message");
6345
+ const service = getNodeResourceData(node.data, "service");
6346
+ const domain = getNodeResourceData(node.data, "domain");
6347
+ const entity = getNodeResourceData(node.data, "entity");
6348
+ const channel = getNodeResourceData(node.data, "channel");
6349
+ const dataProduct = getNodeResourceData(node.data, "dataProduct");
6350
+ const data = getNodeResourceData(node.data, "data");
6351
+ const name = message?.name || message?.id || service?.name || service?.id || domain?.name || domain?.id || entity?.name || entity?.id || channel?.name || channel?.id || dataProduct?.name || dataProduct?.id || data?.name || data?.id || node.data?.name || node.id;
6352
+ const version = message?.version || service?.version || domain?.version || entity?.version || channel?.version || dataProduct?.version || data?.version || node.data?.version;
6353
+ return formatVersionedName(name, version);
6135
6354
  }, []);
6136
- const getNodeTypeColorClass = (0, import_react61.useCallback)((nodeType) => {
6137
- const colorClasses = {
6138
- events: "bg-orange-600 text-white",
6139
- services: "bg-pink-600 text-white",
6140
- flows: "bg-teal-600 text-white",
6141
- commands: "bg-blue-600 text-white",
6142
- queries: "bg-green-600 text-white",
6143
- channels: "bg-gray-600 text-white",
6144
- domains: "bg-yellow-500 text-white",
6145
- externalSystem: "bg-pink-600 text-white",
6146
- actor: "bg-yellow-500 text-white",
6147
- step: "bg-gray-700 text-white",
6148
- user: "bg-yellow-500 text-white",
6149
- custom: "bg-gray-500 text-white",
6150
- field: "bg-cyan-600 text-white",
6151
- messageGroup: "bg-violet-600 text-white",
6152
- messageGroupExpanded: "bg-violet-600 text-white"
6355
+ const getNodeResourceKey = (0, import_react61.useCallback)(
6356
+ (node, label) => {
6357
+ if (node.type === "messageGroup" || node.type === "messageGroupExpanded") {
6358
+ return `${node.type}:${node.id}`.toLowerCase();
6359
+ }
6360
+ const data = node.data;
6361
+ const resource = getNodeResourceData(data, "message") || getNodeResourceData(data, "service") || getNodeResourceData(data, "domain") || getNodeResourceData(data, "entity") || getNodeResourceData(data, "channel") || getNodeResourceData(data, "dataProduct") || data?.data || data;
6362
+ return getResourceKey({
6363
+ type: node.type || "unknown",
6364
+ id: resource?.id,
6365
+ name: resource?.name || resource?.id || label || node.id,
6366
+ version: resource?.version || data?.version
6367
+ });
6368
+ },
6369
+ []
6370
+ );
6371
+ const dedupeSearchSuggestions = (0, import_react61.useCallback)(
6372
+ (suggestions) => {
6373
+ const uniqueSuggestions = [];
6374
+ const indexByResourceKey = /* @__PURE__ */ new Map();
6375
+ suggestions.forEach((suggestion) => {
6376
+ const existingIndex = indexByResourceKey.get(
6377
+ suggestion.resourceKey
6378
+ );
6379
+ if (existingIndex === void 0) {
6380
+ indexByResourceKey.set(
6381
+ suggestion.resourceKey,
6382
+ uniqueSuggestions.length
6383
+ );
6384
+ uniqueSuggestions.push(suggestion);
6385
+ return;
6386
+ }
6387
+ if (suggestion.isGroupedMessage && !uniqueSuggestions[existingIndex].isGroupedMessage) {
6388
+ uniqueSuggestions[existingIndex] = suggestion;
6389
+ }
6390
+ });
6391
+ return uniqueSuggestions;
6392
+ },
6393
+ []
6394
+ );
6395
+ const getSearchSuggestions = (0, import_react61.useCallback)(
6396
+ (nodesToIndex) => {
6397
+ const suggestions = nodesToIndex.flatMap((node) => {
6398
+ const nodeName = getNodeDisplayName(node);
6399
+ const suggestions2 = [
6400
+ {
6401
+ key: node.id,
6402
+ node,
6403
+ label: nodeName,
6404
+ searchText: nodeName,
6405
+ type: node.type || "unknown",
6406
+ resourceKey: getNodeResourceKey(node, nodeName)
6407
+ }
6408
+ ];
6409
+ if (node.type !== "messageGroup") return suggestions2;
6410
+ const groupName = node.data?.groupName || nodeName;
6411
+ const groupedMessages = node.data?.messages || [];
6412
+ groupedMessages.forEach((item, index) => {
6413
+ const message = item.message;
6414
+ const messageName = message?.data?.name || message?.data?.id;
6415
+ if (!messageName) return;
6416
+ const version = message?.data?.version;
6417
+ const label = formatVersionedName(messageName, version);
6418
+ suggestions2.push({
6419
+ key: `${node.id}:${message?.data?.id || messageName}:${version || index}`,
6420
+ node,
6421
+ label,
6422
+ searchText: `${label} ${groupName}`,
6423
+ type: message?.collection || "message",
6424
+ resourceKey: getResourceKey({
6425
+ type: message?.collection || "message",
6426
+ id: message?.data?.id,
6427
+ name: messageName,
6428
+ version
6429
+ }),
6430
+ groupName,
6431
+ isGroupedMessage: true
6432
+ });
6433
+ });
6434
+ return suggestions2;
6435
+ });
6436
+ return dedupeSearchSuggestions(suggestions);
6437
+ },
6438
+ [dedupeSearchSuggestions, getNodeDisplayName, getNodeResourceKey]
6439
+ );
6440
+ const getNodeTypeMeta = (0, import_react61.useCallback)((nodeType) => {
6441
+ const meta = {
6442
+ events: {
6443
+ label: "Event",
6444
+ Icon: import_lucide_react25.Zap,
6445
+ iconClass: "border-orange-500/25 bg-orange-500/10 text-orange-500",
6446
+ badgeClass: "border-orange-500/25 bg-orange-500/10 text-orange-700 dark:text-orange-300"
6447
+ },
6448
+ event: {
6449
+ label: "Event",
6450
+ Icon: import_lucide_react25.Zap,
6451
+ iconClass: "border-orange-500/25 bg-orange-500/10 text-orange-500",
6452
+ badgeClass: "border-orange-500/25 bg-orange-500/10 text-orange-700 dark:text-orange-300"
6453
+ },
6454
+ commands: {
6455
+ label: "Command",
6456
+ Icon: import_lucide_react25.MessageSquare,
6457
+ iconClass: "border-blue-500/25 bg-blue-500/10 text-blue-500",
6458
+ badgeClass: "border-blue-500/25 bg-blue-500/10 text-blue-700 dark:text-blue-300"
6459
+ },
6460
+ command: {
6461
+ label: "Command",
6462
+ Icon: import_lucide_react25.MessageSquare,
6463
+ iconClass: "border-blue-500/25 bg-blue-500/10 text-blue-500",
6464
+ badgeClass: "border-blue-500/25 bg-blue-500/10 text-blue-700 dark:text-blue-300"
6465
+ },
6466
+ queries: {
6467
+ label: "Query",
6468
+ Icon: import_lucide_react25.Search,
6469
+ iconClass: "border-green-500/25 bg-green-500/10 text-green-500",
6470
+ badgeClass: "border-green-500/25 bg-green-500/10 text-green-700 dark:text-green-300"
6471
+ },
6472
+ query: {
6473
+ label: "Query",
6474
+ Icon: import_lucide_react25.Search,
6475
+ iconClass: "border-green-500/25 bg-green-500/10 text-green-500",
6476
+ badgeClass: "border-green-500/25 bg-green-500/10 text-green-700 dark:text-green-300"
6477
+ },
6478
+ services: {
6479
+ label: "Service",
6480
+ Icon: import_lucide_react25.Server,
6481
+ iconClass: "border-pink-500/25 bg-pink-500/10 text-pink-500",
6482
+ badgeClass: "border-pink-500/25 bg-pink-500/10 text-pink-700 dark:text-pink-300"
6483
+ },
6484
+ domains: {
6485
+ label: "Domain",
6486
+ Icon: import_lucide_react25.Blocks,
6487
+ iconClass: "border-yellow-500/25 bg-yellow-500/10 text-yellow-600 dark:text-yellow-400",
6488
+ badgeClass: "border-yellow-500/25 bg-yellow-500/10 text-yellow-700 dark:text-yellow-300"
6489
+ },
6490
+ flows: {
6491
+ label: "Flow",
6492
+ Icon: import_lucide_react25.Workflow,
6493
+ iconClass: "border-teal-500/25 bg-teal-500/10 text-teal-500",
6494
+ badgeClass: "border-teal-500/25 bg-teal-500/10 text-teal-700 dark:text-teal-300"
6495
+ },
6496
+ channels: {
6497
+ label: "Channel",
6498
+ Icon: import_lucide_react25.ListTree,
6499
+ iconClass: "border-gray-500/25 bg-gray-500/10 text-gray-500",
6500
+ badgeClass: "border-gray-500/25 bg-gray-500/10 text-gray-700 dark:text-gray-300"
6501
+ },
6502
+ data: {
6503
+ label: "Data",
6504
+ Icon: import_lucide_react25.Database,
6505
+ iconClass: "border-blue-500/25 bg-blue-500/10 text-blue-500",
6506
+ badgeClass: "border-blue-500/25 bg-blue-500/10 text-blue-700 dark:text-blue-300"
6507
+ },
6508
+ entities: {
6509
+ label: "Entity",
6510
+ Icon: import_lucide_react25.Database,
6511
+ iconClass: "border-blue-500/25 bg-blue-500/10 text-blue-500",
6512
+ badgeClass: "border-blue-500/25 bg-blue-500/10 text-blue-700 dark:text-blue-300"
6513
+ },
6514
+ externalSystem: {
6515
+ label: "External",
6516
+ Icon: import_lucide_react25.Server,
6517
+ iconClass: "border-pink-500/25 bg-pink-500/10 text-pink-500",
6518
+ badgeClass: "border-pink-500/25 bg-pink-500/10 text-pink-700 dark:text-pink-300"
6519
+ },
6520
+ actor: {
6521
+ label: "Actor",
6522
+ Icon: import_lucide_react25.User,
6523
+ iconClass: "border-yellow-500/25 bg-yellow-500/10 text-yellow-600 dark:text-yellow-400",
6524
+ badgeClass: "border-yellow-500/25 bg-yellow-500/10 text-yellow-700 dark:text-yellow-300"
6525
+ },
6526
+ user: {
6527
+ label: "User",
6528
+ Icon: import_lucide_react25.User,
6529
+ iconClass: "border-yellow-500/25 bg-yellow-500/10 text-yellow-600 dark:text-yellow-400",
6530
+ badgeClass: "border-yellow-500/25 bg-yellow-500/10 text-yellow-700 dark:text-yellow-300"
6531
+ },
6532
+ messageGroup: {
6533
+ label: "Group",
6534
+ Icon: import_lucide_react25.Layers,
6535
+ iconClass: "border-violet-500/25 bg-violet-500/10 text-violet-500",
6536
+ badgeClass: "border-violet-500/25 bg-violet-500/10 text-violet-700 dark:text-violet-300"
6537
+ },
6538
+ messageGroupExpanded: {
6539
+ label: "Group",
6540
+ Icon: import_lucide_react25.Layers,
6541
+ iconClass: "border-violet-500/25 bg-violet-500/10 text-violet-500",
6542
+ badgeClass: "border-violet-500/25 bg-violet-500/10 text-violet-700 dark:text-violet-300"
6543
+ }
6544
+ };
6545
+ return meta[nodeType] || {
6546
+ label: nodeType,
6547
+ Icon: import_lucide_react25.Layers,
6548
+ iconClass: "border-gray-500/25 bg-gray-500/10 text-gray-500",
6549
+ badgeClass: "border-gray-500/25 bg-gray-500/10 text-gray-700 dark:text-gray-300"
6153
6550
  };
6154
- return colorClasses[nodeType] || "bg-gray-100 text-gray-700";
6155
6551
  }, []);
6156
6552
  const handleSearchChange = (0, import_react61.useCallback)(
6157
6553
  (event) => {
6158
6554
  const query = event.target.value;
6159
6555
  setSearchQuery(query);
6160
6556
  if (query.length > 0) {
6161
- const filtered = nodes.filter((node) => {
6162
- const nodeName = getNodeDisplayName(node);
6163
- return nodeName.toLowerCase().includes(query.toLowerCase());
6164
- });
6557
+ const search = query.toLowerCase();
6558
+ const filtered = getSearchSuggestions(nodes).filter(
6559
+ (suggestion) => suggestion.searchText.toLowerCase().includes(search)
6560
+ );
6165
6561
  setFilteredSuggestions(filtered);
6166
6562
  setShowSuggestions(true);
6167
6563
  setSelectedSuggestionIndex(-1);
6168
6564
  } else {
6169
- setFilteredSuggestions(nodes);
6565
+ setFilteredSuggestions(getSearchSuggestions(nodes));
6170
6566
  setShowSuggestions(true);
6171
6567
  setSelectedSuggestionIndex(-1);
6172
6568
  }
6173
6569
  },
6174
- [nodes, getNodeDisplayName]
6570
+ [nodes, getSearchSuggestions]
6175
6571
  );
6176
6572
  const handleSearchFocus = (0, import_react61.useCallback)(() => {
6177
- if (searchQuery.length === 0) {
6178
- setFilteredSuggestions(nodes);
6179
- }
6573
+ const suggestions = getSearchSuggestions(nodes);
6574
+ const search = searchQuery.toLowerCase();
6575
+ setFilteredSuggestions(
6576
+ searchQuery.length === 0 ? suggestions : suggestions.filter(
6577
+ (suggestion) => suggestion.searchText.toLowerCase().includes(search)
6578
+ )
6579
+ );
6180
6580
  setShowSuggestions(true);
6181
6581
  setSelectedSuggestionIndex(-1);
6182
- }, [nodes, searchQuery]);
6582
+ }, [nodes, searchQuery, getSearchSuggestions]);
6183
6583
  const handleSuggestionClick = (0, import_react61.useCallback)(
6184
- (node) => {
6185
- setSearchQuery(getNodeDisplayName(node));
6584
+ (suggestion) => {
6585
+ setSearchQuery("");
6586
+ setFilteredSuggestions([]);
6186
6587
  setShowSuggestions(false);
6187
- onNodeSelect(node);
6588
+ setSelectedSuggestionIndex(-1);
6589
+ onNodeSelect(suggestion.node);
6188
6590
  },
6189
- [onNodeSelect, getNodeDisplayName]
6591
+ [onNodeSelect]
6190
6592
  );
6191
6593
  const handleSearchKeyDown = (0, import_react61.useCallback)(
6192
6594
  (event) => {
6193
- if (!showSuggestions || filteredSuggestions.length === 0) return;
6194
6595
  switch (event.key) {
6195
6596
  case "ArrowDown":
6196
6597
  event.preventDefault();
6598
+ if (filteredSuggestions.length === 0) return;
6599
+ setShowSuggestions(true);
6197
6600
  setSelectedSuggestionIndex(
6198
6601
  (prev) => prev < filteredSuggestions.length - 1 ? prev + 1 : 0
6199
6602
  );
6200
6603
  break;
6201
6604
  case "ArrowUp":
6202
6605
  event.preventDefault();
6606
+ if (filteredSuggestions.length === 0) return;
6607
+ setShowSuggestions(true);
6203
6608
  setSelectedSuggestionIndex(
6204
6609
  (prev) => prev > 0 ? prev - 1 : filteredSuggestions.length - 1
6205
6610
  );
6206
6611
  break;
6207
6612
  case "Enter":
6208
6613
  event.preventDefault();
6209
- if (selectedSuggestionIndex >= 0) {
6614
+ if (showSuggestions && selectedSuggestionIndex >= 0 && selectedSuggestionIndex < filteredSuggestions.length) {
6210
6615
  handleSuggestionClick(
6211
6616
  filteredSuggestions[selectedSuggestionIndex]
6212
6617
  );
@@ -6235,6 +6640,31 @@ var VisualiserSearch = (0, import_react61.memo)(
6235
6640
  searchInputRef.current.focus();
6236
6641
  }
6237
6642
  }, [onClear]);
6643
+ (0, import_react61.useEffect)(() => {
6644
+ suggestionItemRefs.current = suggestionItemRefs.current.slice(
6645
+ 0,
6646
+ filteredSuggestions.length
6647
+ );
6648
+ }, [filteredSuggestions.length]);
6649
+ (0, import_react61.useEffect)(() => {
6650
+ if (!showSuggestions || selectedSuggestionIndex < 0) return;
6651
+ const list = suggestionsListRef.current;
6652
+ const item = suggestionItemRefs.current[selectedSuggestionIndex];
6653
+ if (!list || !item) return;
6654
+ const itemTop = item.offsetTop;
6655
+ const itemBottom = itemTop + item.offsetHeight;
6656
+ const visibleTop = list.scrollTop;
6657
+ const visibleBottom = visibleTop + list.clientHeight;
6658
+ if (itemTop < visibleTop) {
6659
+ list.scrollTop = itemTop;
6660
+ } else if (itemBottom > visibleBottom) {
6661
+ list.scrollTop = itemBottom - list.clientHeight;
6662
+ }
6663
+ }, [
6664
+ showSuggestions,
6665
+ selectedSuggestionIndex,
6666
+ filteredSuggestions.length
6667
+ ]);
6238
6668
  (0, import_react61.useEffect)(() => {
6239
6669
  const handleClickOutside = (event) => {
6240
6670
  if (containerRef.current && !containerRef.current.contains(event.target)) {
@@ -6289,28 +6719,53 @@ var VisualiserSearch = (0, import_react61.memo)(
6289
6719
  }
6290
6720
  )
6291
6721
  ] }),
6292
- showSuggestions && filteredSuggestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "absolute top-full left-0 right-0 mt-1 bg-[rgb(var(--ec-card-bg))] border border-[rgb(var(--ec-page-border))] rounded-md shadow-lg z-50 max-h-60 overflow-y-auto", children: filteredSuggestions.map((node, index) => {
6293
- const nodeName = getNodeDisplayName(node);
6294
- const nodeType = node.type || "unknown";
6295
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
6296
- "div",
6297
- {
6298
- onClick: () => handleSuggestionClick(node),
6299
- className: `px-4 py-2 cursor-pointer flex items-center justify-between hover:bg-[rgb(var(--ec-page-border)/0.5)] ${index === selectedSuggestionIndex ? "bg-[rgb(var(--ec-accent-subtle))]" : ""}`,
6300
- children: [
6301
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-sm font-medium text-[rgb(var(--ec-page-text))]", children: nodeName }),
6302
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6303
- "span",
6304
- {
6305
- className: `text-xs capitalize px-2 py-1 rounded ${getNodeTypeColorClass(nodeType)}`,
6306
- children: nodeType
6307
- }
6308
- )
6309
- ]
6310
- },
6311
- node.id
6312
- );
6313
- }) })
6722
+ showSuggestions && filteredSuggestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6723
+ "div",
6724
+ {
6725
+ ref: suggestionsListRef,
6726
+ className: "absolute top-full left-0 right-0 mt-1 bg-[rgb(var(--ec-card-bg))] border border-[rgb(var(--ec-page-border))] rounded-md shadow-lg z-50 max-h-60 overflow-y-auto",
6727
+ children: filteredSuggestions.map((suggestion, index) => {
6728
+ const nodeTypeMeta = getNodeTypeMeta(suggestion.type);
6729
+ const Icon = nodeTypeMeta.Icon;
6730
+ const isSelected = index === selectedSuggestionIndex;
6731
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
6732
+ "div",
6733
+ {
6734
+ ref: (element) => {
6735
+ suggestionItemRefs.current[index] = element;
6736
+ },
6737
+ onClick: () => handleSuggestionClick(suggestion),
6738
+ onMouseEnter: () => setSelectedSuggestionIndex(index),
6739
+ className: `px-3 py-2 cursor-pointer flex items-start gap-3 ${isSelected ? "bg-[rgb(var(--ec-accent-subtle))] outline outline-1 -outline-offset-1 outline-[rgb(var(--ec-accent))]" : "hover:bg-[rgb(var(--ec-page-border)/0.5)]"}`,
6740
+ children: [
6741
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6742
+ "span",
6743
+ {
6744
+ className: `mt-0.5 flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md border ${nodeTypeMeta.iconClass}`,
6745
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(Icon, { className: "h-3.5 w-3.5" })
6746
+ }
6747
+ ),
6748
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "min-w-0 flex-1", children: [
6749
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "block truncate text-sm font-medium text-[rgb(var(--ec-page-text))]", children: suggestion.label }),
6750
+ suggestion.isGroupedMessage && suggestion.groupName && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "mt-0.5 block truncate text-xs text-[rgb(var(--ec-page-text-muted))]", children: [
6751
+ "in ",
6752
+ suggestion.groupName
6753
+ ] })
6754
+ ] }),
6755
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6756
+ "span",
6757
+ {
6758
+ className: `mt-0.5 flex-shrink-0 rounded border px-2 py-0.5 text-xs font-medium ${nodeTypeMeta.badgeClass}`,
6759
+ children: nodeTypeMeta.label
6760
+ }
6761
+ )
6762
+ ]
6763
+ },
6764
+ `${suggestion.key}:${index}`
6765
+ );
6766
+ })
6767
+ }
6768
+ )
6314
6769
  ] });
6315
6770
  }
6316
6771
  )
@@ -6565,7 +7020,7 @@ var StepWalkthrough_default = (0, import_react62.memo)(function StepWalkthrough(
6565
7020
  // src/components/StudioModal.tsx
6566
7021
  var import_react63 = require("react");
6567
7022
  var Dialog2 = __toESM(require("@radix-ui/react-dialog"));
6568
- var import_lucide_react25 = require("lucide-react");
7023
+ var import_lucide_react26 = require("lucide-react");
6569
7024
  var import_react64 = require("@xyflow/react");
6570
7025
 
6571
7026
  // src/utils/export-node-graph.ts
@@ -6690,10 +7145,10 @@ var StudioModal = ({ isOpen, onClose }) => {
6690
7145
  onClick: handleCopyToClipboard,
6691
7146
  className: `w-full flex items-center justify-center space-x-2 px-4 py-2 text-sm font-medium rounded-md border transition-colors ${copySuccess ? "bg-green-50 border-green-200 text-green-700" : "bg-white border-gray-300 text-gray-700 hover:bg-gray-50"}`,
6692
7147
  children: copySuccess ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
6693
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react25.CheckIcon, { className: "w-4 h-4" }),
7148
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react26.CheckIcon, { className: "w-4 h-4" }),
6694
7149
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { children: "Copied!" })
6695
7150
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
6696
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react25.ClipboardIcon, { className: "w-4 h-4" }),
7151
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react26.ClipboardIcon, { className: "w-4 h-4" }),
6697
7152
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { children: "Copy diagram to clipboard" })
6698
7153
  ] })
6699
7154
  }
@@ -6708,7 +7163,7 @@ var StudioModal = ({ isOpen, onClose }) => {
6708
7163
  onClick: handleOpenStudio,
6709
7164
  className: "w-full flex items-center justify-center space-x-2 px-4 py-2 bg-[rgb(var(--ec-accent))] text-white text-sm font-medium rounded-md hover:bg-[rgb(var(--ec-accent-hover))] transition-colors",
6710
7165
  children: [
6711
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react25.ExternalLinkIcon, { className: "w-4 h-4" }),
7166
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react26.ExternalLinkIcon, { className: "w-4 h-4" }),
6712
7167
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { children: "Open EventCatalog Studio" })
6713
7168
  ]
6714
7169
  }
@@ -6733,7 +7188,7 @@ var StudioModal_default = StudioModal;
6733
7188
  // src/components/FocusModeModal.tsx
6734
7189
  var import_react69 = require("react");
6735
7190
  var Dialog3 = __toESM(require("@radix-ui/react-dialog"));
6736
- var import_lucide_react27 = require("lucide-react");
7191
+ var import_lucide_react28 = require("lucide-react");
6737
7192
  var import_react70 = require("@xyflow/react");
6738
7193
 
6739
7194
  // src/components/FocusMode/FocusModeContent.tsx
@@ -6847,7 +7302,7 @@ function getNodeDocUrl(node) {
6847
7302
 
6848
7303
  // src/components/FocusMode/FocusModeNodeActions.tsx
6849
7304
  var import_react65 = require("@xyflow/react");
6850
- var import_lucide_react26 = require("lucide-react");
7305
+ var import_lucide_react27 = require("lucide-react");
6851
7306
  var import_jsx_runtime35 = require("react/jsx-runtime");
6852
7307
  var FocusModeNodeActions = ({
6853
7308
  node,
@@ -6894,7 +7349,7 @@ var FocusModeNodeActions = ({
6894
7349
  className: "flex items-center justify-center rounded-md text-[rgb(var(--ec-icon-color))] hover:text-[rgb(var(--ec-accent))] hover:bg-[rgb(var(--ec-accent-subtle))] transition-colors",
6895
7350
  style: { width: buttonSize, height: buttonSize },
6896
7351
  title: "View documentation",
6897
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react26.FileText, { style: { width: iconSize, height: iconSize } })
7352
+ children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react27.FileText, { style: { width: iconSize, height: iconSize } })
6898
7353
  }
6899
7354
  )
6900
7355
  }
@@ -6922,7 +7377,7 @@ var FocusModeNodeActions = ({
6922
7377
  className: "flex items-center justify-center rounded-md text-[rgb(var(--ec-icon-color))] hover:text-[rgb(var(--ec-accent))] hover:bg-[rgb(var(--ec-accent-subtle))] transition-colors",
6923
7378
  style: { width: buttonSize, height: buttonSize },
6924
7379
  title: "View documentation",
6925
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react26.FileText, { style: { width: iconSize, height: iconSize } })
7380
+ children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react27.FileText, { style: { width: iconSize, height: iconSize } })
6926
7381
  }
6927
7382
  ),
6928
7383
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
@@ -6932,7 +7387,7 @@ var FocusModeNodeActions = ({
6932
7387
  className: "flex items-center justify-center rounded-md text-[rgb(var(--ec-icon-color))] hover:text-[rgb(var(--ec-accent))] hover:bg-[rgb(var(--ec-accent-subtle))] transition-colors",
6933
7388
  style: { width: buttonSize, height: buttonSize },
6934
7389
  title: "Focus on this node",
6935
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react26.ArrowRightLeft, { style: { width: iconSize, height: iconSize } })
7390
+ children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react27.ArrowRightLeft, { style: { width: iconSize, height: iconSize } })
6936
7391
  }
6937
7392
  )
6938
7393
  ]
@@ -7331,7 +7786,7 @@ var FocusModeModal = ({
7331
7786
  background: isDark ? "rgba(59, 130, 246, 0.18)" : "rgba(59, 130, 246, 0.12)"
7332
7787
  },
7333
7788
  children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
7334
- import_lucide_react27.FocusIcon,
7789
+ import_lucide_react28.FocusIcon,
7335
7790
  {
7336
7791
  style: {
7337
7792
  width: 20,
@@ -7383,7 +7838,7 @@ var FocusModeModal = ({
7383
7838
  color: isDark ? "#94a3b8" : "#64748b"
7384
7839
  },
7385
7840
  "aria-label": "Close",
7386
- children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react27.XIcon, { style: { width: 20, height: 20 } })
7841
+ children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react28.XIcon, { style: { width: 20, height: 20 } })
7387
7842
  }
7388
7843
  ) })
7389
7844
  ]
@@ -7411,7 +7866,7 @@ var FocusModeModal_default = FocusModeModal;
7411
7866
 
7412
7867
  // src/components/MermaidView.tsx
7413
7868
  var import_react71 = require("react");
7414
- var import_lucide_react28 = require("lucide-react");
7869
+ var import_lucide_react29 = require("lucide-react");
7415
7870
 
7416
7871
  // src/utils/export-mermaid.ts
7417
7872
  var NODE_SHAPE_MAP = {
@@ -7805,7 +8260,7 @@ var MermaidView = ({
7805
8260
  onClick: handleCopyToClipboard,
7806
8261
  className: `p-2.5 rounded-md shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[rgb(var(--ec-accent))] transition-all duration-150 ${copySuccess ? "bg-green-500 text-white scale-110" : "bg-[rgb(var(--ec-card-bg))] hover:bg-[rgb(var(--ec-page-border))/0.5] text-[rgb(var(--ec-icon-color))] hover:scale-105"}`,
7807
8262
  "aria-label": copySuccess ? "Copied!" : "Copy Mermaid code",
7808
- children: copySuccess ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_lucide_react28.CheckIcon, { className: "h-5 w-5" }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_lucide_react28.ClipboardIcon, { className: "h-5 w-5" })
8263
+ children: copySuccess ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_lucide_react29.CheckIcon, { className: "h-5 w-5" }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_lucide_react29.ClipboardIcon, { className: "h-5 w-5" })
7809
8264
  }
7810
8265
  ),
7811
8266
  /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "absolute top-full right-0 mt-2 px-2 py-1 bg-[rgb(var(--ec-page-text))] text-[rgb(var(--ec-page-bg))] text-xs rounded shadow-lg opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none z-50", children: copySuccess ? "Copied!" : "Copy Mermaid code" })
@@ -8111,7 +8566,7 @@ var useChannelVisibility = ({
8111
8566
  // src/components/VisualizerDropdownContent.tsx
8112
8567
  var import_react74 = require("react");
8113
8568
  var DropdownMenu = __toESM(require("@radix-ui/react-dropdown-menu"));
8114
- var import_lucide_react29 = require("lucide-react");
8569
+ var import_lucide_react30 = require("lucide-react");
8115
8570
  var import_outline2 = require("@heroicons/react/24/outline");
8116
8571
  var import_jsx_runtime40 = require("react/jsx-runtime");
8117
8572
  var VisualizerDropdownContent = (0, import_react74.memo)(
@@ -8166,7 +8621,7 @@ var VisualizerDropdownContent = (0, import_react74.memo)(
8166
8621
  return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
8167
8622
  /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(DropdownMenu.Sub, { children: [
8168
8623
  /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(DropdownMenu.SubTrigger, { className: "flex items-center px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer transition-colors gap-2 outline-none", children: [
8169
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react29.Grid3x3, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8624
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react30.Grid3x3, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8170
8625
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "flex-1 font-normal", children: "Canvas" }),
8171
8626
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
8172
8627
  "svg",
@@ -8201,7 +8656,7 @@ var VisualizerDropdownContent = (0, import_react74.memo)(
8201
8656
  onCheckedChange: setIsMermaidView,
8202
8657
  className: "flex items-center px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer transition-colors gap-2",
8203
8658
  children: [
8204
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react29.Code, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8659
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react30.Code, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8205
8660
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "flex-1 font-normal", children: "Render as mermaid" }),
8206
8661
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
8207
8662
  "div",
@@ -8226,7 +8681,7 @@ var VisualizerDropdownContent = (0, import_react74.memo)(
8226
8681
  onCheckedChange: toggleAnimateMessages,
8227
8682
  className: "flex items-center px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer transition-colors gap-2",
8228
8683
  children: [
8229
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react29.Zap, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8684
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react30.Zap, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8230
8685
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "flex-1 font-normal", children: "Simulate Messages" }),
8231
8686
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
8232
8687
  "div",
@@ -8250,7 +8705,7 @@ var VisualizerDropdownContent = (0, import_react74.memo)(
8250
8705
  onCheckedChange: toggleChannelsVisibility,
8251
8706
  className: "flex items-center px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer transition-colors gap-2",
8252
8707
  children: [
8253
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react29.EyeOff, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8708
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react30.EyeOff, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8254
8709
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "flex-1 font-normal", children: "Hide channels" }),
8255
8710
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
8256
8711
  "div",
@@ -8274,7 +8729,7 @@ var VisualizerDropdownContent = (0, import_react74.memo)(
8274
8729
  onCheckedChange: setShowMinimap,
8275
8730
  className: "flex items-center px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer transition-colors gap-2",
8276
8731
  children: [
8277
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react29.Map, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8732
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react30.Map, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8278
8733
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "flex-1 font-normal", children: "Show minimap" }),
8279
8734
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
8280
8735
  "div",
@@ -8298,7 +8753,7 @@ var VisualizerDropdownContent = (0, import_react74.memo)(
8298
8753
  onClick: handleFitView,
8299
8754
  className: "px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors",
8300
8755
  children: [
8301
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react29.Maximize2, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8756
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react30.Maximize2, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8302
8757
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "flex-1 font-normal", children: "Fit to view" })
8303
8758
  ]
8304
8759
  }
@@ -8317,7 +8772,7 @@ var VisualizerDropdownContent = (0, import_react74.memo)(
8317
8772
  },
8318
8773
  className: "px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors",
8319
8774
  children: [
8320
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react29.Search, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8775
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react30.Search, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8321
8776
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "flex-1 font-normal", children: "Find on canvas" })
8322
8777
  ]
8323
8778
  }
@@ -8332,7 +8787,7 @@ var VisualizerDropdownContent = (0, import_react74.memo)(
8332
8787
  onClick: onOpenNotes,
8333
8788
  className: "px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors",
8334
8789
  children: [
8335
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react29.MessageCircle, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8790
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react30.MessageCircle, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8336
8791
  /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "flex-1 font-normal", children: [
8337
8792
  "View notes (",
8338
8793
  notesCount,
@@ -8343,7 +8798,7 @@ var VisualizerDropdownContent = (0, import_react74.memo)(
8343
8798
  ),
8344
8799
  isDevMode && onSaveLayout && /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(DropdownMenu.Sub, { children: [
8345
8800
  /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(DropdownMenu.SubTrigger, { className: "flex items-center px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer transition-colors gap-2 outline-none", children: [
8346
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react29.Save, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8801
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react30.Save, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8347
8802
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "flex-1 font-normal", children: "Layout" }),
8348
8803
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "text-[10px] text-amber-600 font-medium", children: "DEV" }),
8349
8804
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
@@ -8379,7 +8834,7 @@ var VisualizerDropdownContent = (0, import_react74.memo)(
8379
8834
  disabled: layoutStatus !== "idle",
8380
8835
  className: "px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
8381
8836
  children: [
8382
- layoutStatus === "saving" ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react29.Loader2, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react29.Save, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8837
+ layoutStatus === "saving" ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react30.Loader2, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react30.Save, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8383
8838
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "flex-1 font-normal", children: layoutStatus === "saving" ? "Saving..." : "Save Layout" })
8384
8839
  ]
8385
8840
  }
@@ -8391,7 +8846,7 @@ var VisualizerDropdownContent = (0, import_react74.memo)(
8391
8846
  disabled: layoutStatus !== "idle",
8392
8847
  className: "px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
8393
8848
  children: [
8394
- layoutStatus === "resetting" ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react29.Loader2, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react29.RotateCcw, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8849
+ layoutStatus === "resetting" ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react30.Loader2, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react30.RotateCcw, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8395
8850
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "flex-1 font-normal", children: layoutStatus === "resetting" ? "Resetting..." : "Reset Layout" })
8396
8851
  ]
8397
8852
  }
@@ -8408,7 +8863,7 @@ var VisualizerDropdownContent = (0, import_react74.memo)(
8408
8863
  onClick: openChat,
8409
8864
  className: "px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors",
8410
8865
  children: [
8411
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react29.Sparkles, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8866
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react30.Sparkles, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8412
8867
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "flex-1 font-normal", children: "Ask a question" })
8413
8868
  ]
8414
8869
  }
@@ -8421,7 +8876,7 @@ var VisualizerDropdownContent = (0, import_react74.memo)(
8421
8876
  onClick: handleCopyArchitectureCode,
8422
8877
  className: "px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors",
8423
8878
  children: [
8424
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react29.Code, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8879
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react30.Code, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8425
8880
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "flex-1 font-normal", children: "Copy as mermaid" })
8426
8881
  ]
8427
8882
  }
@@ -8443,7 +8898,7 @@ var VisualizerDropdownContent = (0, import_react74.memo)(
8443
8898
  onClick: () => setIsShareModalOpen(true),
8444
8899
  className: "px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors",
8445
8900
  children: [
8446
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react29.Share2, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8901
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react30.Share2, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8447
8902
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "flex-1 font-normal", children: "Share Link" })
8448
8903
  ]
8449
8904
  }
@@ -8467,7 +8922,7 @@ var VisualizerDropdownContent = (0, import_react74.memo)(
8467
8922
  onClick: openStudioModal,
8468
8923
  className: "px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors",
8469
8924
  children: [
8470
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react29.ExternalLink, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8925
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react30.ExternalLink, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8471
8926
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "flex-1 font-normal", children: "Open in EventCatalog Studio" })
8472
8927
  ]
8473
8928
  }
@@ -9010,30 +9465,144 @@ function flatLayout(nodes, edges, graphOpts, nodeSize, style) {
9010
9465
  return { nodes: layoutNodes, edges: layoutEdges };
9011
9466
  }
9012
9467
 
9468
+ // src/utils/local-packing.ts
9469
+ var toNumber = (value) => {
9470
+ if (typeof value === "number") return value;
9471
+ if (typeof value === "string") {
9472
+ const parsed = Number.parseFloat(value);
9473
+ return Number.isFinite(parsed) ? parsed : void 0;
9474
+ }
9475
+ return void 0;
9476
+ };
9477
+ var getPackableNodeSize = (node) => ({
9478
+ width: toNumber(node.measured?.width) ?? toNumber(node.width) ?? toNumber(node.style?.width) ?? 260,
9479
+ height: toNumber(node.measured?.height) ?? toNumber(node.height) ?? toNumber(node.style?.height) ?? 140
9480
+ });
9481
+ var rectsIntersect = (a, b, gap = 0) => a.x < b.x + b.width + gap && a.x + a.width + gap > b.x && a.y < b.y + b.height + gap && a.y + a.height + gap > b.y;
9482
+ var toRect = (node, y = node.position.y) => {
9483
+ const size = getPackableNodeSize(node);
9484
+ return {
9485
+ x: node.position.x,
9486
+ y,
9487
+ width: size.width,
9488
+ height: size.height
9489
+ };
9490
+ };
9491
+ var packNodesAroundBounds = ({
9492
+ nodes,
9493
+ movableNodeIds,
9494
+ protectedBounds,
9495
+ groupNodeId,
9496
+ gap = 40
9497
+ }) => {
9498
+ const movableNodes = nodes.filter((node) => movableNodeIds.has(node.id)).sort((a, b) => a.position.y - b.position.y);
9499
+ const movableIds = new Set(movableNodes.map((node) => node.id));
9500
+ const occupiedRects = [
9501
+ protectedBounds,
9502
+ ...nodes.filter(
9503
+ (node) => !node.parentId && node.id !== groupNodeId && !movableIds.has(node.id)
9504
+ ).map((node) => toRect(node))
9505
+ ];
9506
+ const plannedPositions = /* @__PURE__ */ new Map();
9507
+ const groupCenterY = protectedBounds.y + protectedBounds.height / 2;
9508
+ const placeNode = (node) => {
9509
+ const size = getPackableNodeSize(node);
9510
+ const nodeCenterY = node.position.y + size.height / 2;
9511
+ const moveDirection = nodeCenterY >= groupCenterY ? 1 : -1;
9512
+ let y = moveDirection > 0 ? Math.max(
9513
+ node.position.y,
9514
+ protectedBounds.y + protectedBounds.height + gap
9515
+ ) : Math.min(node.position.y, protectedBounds.y - size.height - gap);
9516
+ let attempts = 0;
9517
+ while (attempts < occupiedRects.length + 8) {
9518
+ const rect = {
9519
+ x: node.position.x,
9520
+ y,
9521
+ width: size.width,
9522
+ height: size.height
9523
+ };
9524
+ const collision = occupiedRects.find(
9525
+ (occupied) => rectsIntersect(rect, occupied, gap)
9526
+ );
9527
+ if (!collision) {
9528
+ occupiedRects.push(rect);
9529
+ plannedPositions.set(node.id, { ...node.position, y });
9530
+ return;
9531
+ }
9532
+ y = moveDirection > 0 ? collision.y + collision.height + gap : collision.y - size.height - gap;
9533
+ attempts += 1;
9534
+ }
9535
+ occupiedRects.push({
9536
+ x: node.position.x,
9537
+ y,
9538
+ width: size.width,
9539
+ height: size.height
9540
+ });
9541
+ plannedPositions.set(node.id, { ...node.position, y });
9542
+ };
9543
+ const below = movableNodes.filter((node) => {
9544
+ const size = getPackableNodeSize(node);
9545
+ return node.position.y + size.height / 2 >= groupCenterY;
9546
+ });
9547
+ const belowIds = new Set(below.map((node) => node.id));
9548
+ const above = movableNodes.filter((node) => !belowIds.has(node.id)).reverse();
9549
+ below.forEach(placeNode);
9550
+ above.forEach(placeNode);
9551
+ return plannedPositions;
9552
+ };
9553
+
9554
+ // src/utils/message-group-expansion.ts
9555
+ var getExpandedMessageGroupNode = (nodes, groupNodeId) => nodes.find(
9556
+ (node) => node.id === groupNodeId && node.type === "messageGroupExpanded"
9557
+ );
9558
+ var buildMessageGroupExpansionNodes = ({
9559
+ currentNodes,
9560
+ groupNodeId,
9561
+ expandedContainerNode,
9562
+ childNodes,
9563
+ downstreamNodes,
9564
+ getDownstreamPosition
9565
+ }) => {
9566
+ const withoutExistingGroup = currentNodes.filter(
9567
+ (node) => node.id !== groupNodeId && node.parentId !== groupNodeId
9568
+ );
9569
+ const existingIds = new Set(withoutExistingGroup.map((node) => node.id));
9570
+ const newDownstream = downstreamNodes.filter((node) => !existingIds.has(node.id)).map((node, index) => ({
9571
+ ...node,
9572
+ position: getDownstreamPosition(node, index)
9573
+ }));
9574
+ return [
9575
+ ...withoutExistingGroup,
9576
+ expandedContainerNode,
9577
+ ...childNodes,
9578
+ ...newDownstream
9579
+ ];
9580
+ };
9581
+
9013
9582
  // src/components/NotesToolbarButton.tsx
9014
9583
  var import_react77 = require("react");
9015
- var import_lucide_react30 = require("lucide-react");
9584
+ var import_lucide_react31 = require("lucide-react");
9016
9585
  var import_react78 = require("@xyflow/react");
9017
9586
  var Dialog4 = __toESM(require("@radix-ui/react-dialog"));
9018
9587
  var import_jsx_runtime42 = require("react/jsx-runtime");
9019
9588
  var NODE_TYPE_META = {
9020
- service: { icon: import_lucide_react30.ServerIcon, color: "#ec4899", label: "Service" },
9021
- services: { icon: import_lucide_react30.ServerIcon, color: "#ec4899", label: "Service" },
9022
- event: { icon: import_lucide_react30.Zap, color: "#f97316", label: "Event" },
9023
- events: { icon: import_lucide_react30.Zap, color: "#f97316", label: "Event" },
9024
- command: { icon: import_lucide_react30.MessageSquare, color: "#3b82f6", label: "Command" },
9025
- commands: { icon: import_lucide_react30.MessageSquare, color: "#3b82f6", label: "Command" },
9026
- query: { icon: import_lucide_react30.Search, color: "#22c55e", label: "Query" },
9027
- queries: { icon: import_lucide_react30.Search, color: "#22c55e", label: "Query" },
9028
- channel: { icon: import_lucide_react30.ArrowRightLeft, color: "#6b7280", label: "Channel" },
9029
- channels: { icon: import_lucide_react30.ArrowRightLeft, color: "#6b7280", label: "Channel" },
9030
- data: { icon: import_lucide_react30.Database, color: "#3b82f6", label: "Data" },
9031
- "data-products": { icon: import_lucide_react30.Package, color: "#6366f1", label: "Data Product" },
9032
- externalSystem: { icon: import_lucide_react30.Globe, color: "#ec4899", label: "External System" },
9033
- actor: { icon: import_lucide_react30.User, color: "#eab308", label: "Actor" },
9034
- view: { icon: import_lucide_react30.MonitorIcon, color: "#8b5cf6", label: "View" },
9035
- domain: { icon: import_lucide_react30.BoxesIcon, color: "#14b8a6", label: "Domain" },
9036
- domains: { icon: import_lucide_react30.BoxesIcon, color: "#14b8a6", label: "Domain" }
9589
+ service: { icon: import_lucide_react31.ServerIcon, color: "#ec4899", label: "Service" },
9590
+ services: { icon: import_lucide_react31.ServerIcon, color: "#ec4899", label: "Service" },
9591
+ event: { icon: import_lucide_react31.Zap, color: "#f97316", label: "Event" },
9592
+ events: { icon: import_lucide_react31.Zap, color: "#f97316", label: "Event" },
9593
+ command: { icon: import_lucide_react31.MessageSquare, color: "#3b82f6", label: "Command" },
9594
+ commands: { icon: import_lucide_react31.MessageSquare, color: "#3b82f6", label: "Command" },
9595
+ query: { icon: import_lucide_react31.Search, color: "#22c55e", label: "Query" },
9596
+ queries: { icon: import_lucide_react31.Search, color: "#22c55e", label: "Query" },
9597
+ channel: { icon: import_lucide_react31.ArrowRightLeft, color: "#6b7280", label: "Channel" },
9598
+ channels: { icon: import_lucide_react31.ArrowRightLeft, color: "#6b7280", label: "Channel" },
9599
+ data: { icon: import_lucide_react31.Database, color: "#3b82f6", label: "Data" },
9600
+ "data-products": { icon: import_lucide_react31.Package, color: "#6366f1", label: "Data Product" },
9601
+ externalSystem: { icon: import_lucide_react31.Globe, color: "#ec4899", label: "External System" },
9602
+ actor: { icon: import_lucide_react31.User, color: "#eab308", label: "Actor" },
9603
+ view: { icon: import_lucide_react31.MonitorIcon, color: "#8b5cf6", label: "View" },
9604
+ domain: { icon: import_lucide_react31.BoxesIcon, color: "#14b8a6", label: "Domain" },
9605
+ domains: { icon: import_lucide_react31.BoxesIcon, color: "#14b8a6", label: "Domain" }
9037
9606
  };
9038
9607
  function getNodeMeta(nodeType) {
9039
9608
  if (!nodeType) return null;
@@ -9172,7 +9741,7 @@ function PriorityBadge({ priority }) {
9172
9741
  lineHeight: 1.4
9173
9742
  },
9174
9743
  children: [
9175
- isUrgent && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react30.AlertTriangle, { style: { width: 9, height: 9 }, strokeWidth: 2.5 }),
9744
+ isUrgent && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react31.AlertTriangle, { style: { width: 9, height: 9 }, strokeWidth: 2.5 }),
9176
9745
  p.label
9177
9746
  ]
9178
9747
  }
@@ -9367,7 +9936,7 @@ function AllNotesModal({
9367
9936
  flexShrink: 0
9368
9937
  },
9369
9938
  children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
9370
- import_lucide_react30.MessageCircle,
9939
+ import_lucide_react31.MessageCircle,
9371
9940
  {
9372
9941
  style: { width: 16, height: 16, color: "#64748b" },
9373
9942
  strokeWidth: 2.5
@@ -9428,7 +9997,7 @@ function AllNotesModal({
9428
9997
  flexShrink: 0
9429
9998
  },
9430
9999
  "aria-label": "Close",
9431
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react30.X, { style: { width: 15, height: 15 } })
10000
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react31.X, { style: { width: 15, height: 15 } })
9432
10001
  }
9433
10002
  ) })
9434
10003
  ]
@@ -9448,7 +10017,7 @@ function AllNotesModal({
9448
10017
  children: noteGroups.map((group, i) => {
9449
10018
  const isActive = i === selectedIdx;
9450
10019
  const meta = getNodeMeta(group.nodeType);
9451
- const IconComp = meta?.icon || import_lucide_react30.MessageCircle;
10020
+ const IconComp = meta?.icon || import_lucide_react31.MessageCircle;
9452
10021
  const iconColor = meta?.color || "#64748b";
9453
10022
  return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
9454
10023
  "button",
@@ -9537,7 +10106,7 @@ function AllNotesModal({
9537
10106
  )
9538
10107
  ] }),
9539
10108
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
9540
- import_lucide_react30.ChevronRight,
10109
+ import_lucide_react31.ChevronRight,
9541
10110
  {
9542
10111
  style: {
9543
10112
  width: 14,
@@ -9589,7 +10158,7 @@ function AllNotesModal({
9589
10158
  children: [
9590
10159
  (() => {
9591
10160
  const meta = getNodeMeta(selected.nodeType);
9592
- const Icon = meta?.icon || import_lucide_react30.MessageCircle;
10161
+ const Icon = meta?.icon || import_lucide_react31.MessageCircle;
9593
10162
  const color = meta?.color || "#64748b";
9594
10163
  return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
9595
10164
  "div",
@@ -9680,7 +10249,7 @@ function AllNotesModal({
9680
10249
  e.currentTarget.style.color = "#475569";
9681
10250
  },
9682
10251
  children: [
9683
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react30.Locate, { style: { width: 12, height: 12 } }),
10252
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react31.Locate, { style: { width: 12, height: 12 } }),
9684
10253
  "Find on canvas"
9685
10254
  ]
9686
10255
  }
@@ -9932,7 +10501,7 @@ var NodeGraphBuilder = ({
9932
10501
  );
9933
10502
  const [nodes, setNodes, onNodesChange] = (0, import_react80.useNodesState)(initialNodes);
9934
10503
  const [edges, setEdges, onEdgesChange] = (0, import_react80.useEdgesState)(initialEdges);
9935
- const { fitView, getNodes } = (0, import_react80.useReactFlow)();
10504
+ const { fitView, getNodes, getIntersectingNodes, getZoom, setCenter } = (0, import_react80.useReactFlow)();
9936
10505
  (0, import_react79.useEffect)(() => {
9937
10506
  setNodes(initialNodes);
9938
10507
  setEdges(initialEdges);
@@ -10072,57 +10641,107 @@ var NodeGraphBuilder = ({
10072
10641
  wrapper.classList.add("ec-animating-layout");
10073
10642
  setTimeout(() => wrapper.classList.remove("ec-animating-layout"), 400);
10074
10643
  }, []);
10075
- const relayoutGraph = (0, import_react79.useCallback)((nextNodes, nextEdges) => {
10076
- const g = new import_dagre3.default.graphlib.Graph({ compound: true });
10077
- g.setGraph({ rankdir: "LR", ranksep: 300, nodesep: 50 });
10078
- g.setDefaultEdgeLabel(() => ({}));
10079
- nextNodes.forEach((node) => {
10080
- if (node.parentId) return;
10081
- const w = node.style?.width || (node.type === "messageGroupExpanded" ? 380 : 150);
10082
- const h = node.style?.height || (node.type === "messageGroupExpanded" ? 0 : 120);
10083
- if (node.type === "messageGroupExpanded") {
10084
- const children = nextNodes.filter((n) => n.parentId === node.id);
10085
- const childHeight = children.length * 190 + 100;
10086
- g.setNode(node.id, { width: w, height: childHeight });
10087
- } else {
10088
- g.setNode(node.id, { width: w, height: h });
10089
- }
10090
- });
10091
- nextEdges.forEach((edge) => {
10092
- const sourceNode = nextNodes.find((n) => n.id === edge.source);
10093
- const targetNode = nextNodes.find((n) => n.id === edge.target);
10094
- const sourceTop = sourceNode?.parentId || edge.source;
10095
- const targetTop = targetNode?.parentId || edge.target;
10096
- if (g.hasNode(sourceTop) && g.hasNode(targetTop) && sourceTop !== targetTop) {
10097
- g.setEdge(sourceTop, targetTop);
10098
- }
10099
- });
10100
- import_dagre3.default.layout(g);
10101
- const positioned = nextNodes.map((node) => {
10102
- if (node.parentId) {
10103
- const parent = nextNodes.find((n) => n.id === node.parentId);
10104
- if (parent?.type === "flowExpanded") {
10105
- return node;
10644
+ const relayoutGraph = (0, import_react79.useCallback)(
10645
+ (nextNodes, nextEdges, anchor) => {
10646
+ const g = new import_dagre3.default.graphlib.Graph({ compound: true });
10647
+ g.setGraph({ rankdir: "LR", ranksep: 300, nodesep: 50 });
10648
+ g.setDefaultEdgeLabel(() => ({}));
10649
+ nextNodes.forEach((node) => {
10650
+ if (node.parentId) return;
10651
+ const w = node.style?.width || (node.type === "messageGroupExpanded" ? 380 : 150);
10652
+ const h = node.style?.height || (node.type === "messageGroupExpanded" ? 0 : 120);
10653
+ if (node.type === "messageGroupExpanded") {
10654
+ const children = nextNodes.filter((n) => n.parentId === node.id);
10655
+ const childHeight = children.length * 190 + 100;
10656
+ g.setNode(node.id, { width: w, height: childHeight });
10657
+ } else {
10658
+ g.setNode(node.id, { width: w, height: h });
10659
+ }
10660
+ });
10661
+ nextEdges.forEach((edge) => {
10662
+ const sourceNode = nextNodes.find((n) => n.id === edge.source);
10663
+ const targetNode = nextNodes.find((n) => n.id === edge.target);
10664
+ const sourceTop = sourceNode?.parentId || edge.source;
10665
+ const targetTop = targetNode?.parentId || edge.target;
10666
+ if (g.hasNode(sourceTop) && g.hasNode(targetTop) && sourceTop !== targetTop) {
10667
+ g.setEdge(sourceTop, targetTop);
10106
10668
  }
10107
- const parentWidth = parent?.style?.width || 380;
10108
- const childWidth = 240;
10109
- const xOffset = Math.max(20, (parentWidth - childWidth) / 2);
10110
- const siblings = nextNodes.filter((n) => n.parentId === node.parentId);
10111
- const index = siblings.indexOf(node);
10669
+ });
10670
+ import_dagre3.default.layout(g);
10671
+ const positioned = nextNodes.map((node) => {
10672
+ if (node.parentId) {
10673
+ const parent = nextNodes.find((n) => n.id === node.parentId);
10674
+ if (parent?.type === "flowExpanded") {
10675
+ return node;
10676
+ }
10677
+ const parentWidth = parent?.style?.width || 380;
10678
+ const childWidth = 240;
10679
+ const xOffset = Math.max(20, (parentWidth - childWidth) / 2);
10680
+ const siblings = nextNodes.filter(
10681
+ (n) => n.parentId === node.parentId
10682
+ );
10683
+ const index = siblings.indexOf(node);
10684
+ return {
10685
+ ...node,
10686
+ position: { x: xOffset, y: 70 + index * 190 }
10687
+ };
10688
+ }
10689
+ const pos = g.node(node.id);
10690
+ if (!pos) return node;
10112
10691
  return {
10113
10692
  ...node,
10114
- position: { x: xOffset, y: 70 + index * 190 }
10693
+ position: { x: pos.x - pos.width / 2, y: pos.y - pos.height / 2 }
10115
10694
  };
10116
- }
10117
- const pos = g.node(node.id);
10118
- if (!pos) return node;
10119
- return {
10120
- ...node,
10121
- position: { x: pos.x - pos.width / 2, y: pos.y - pos.height / 2 }
10695
+ });
10696
+ if (!anchor) return positioned;
10697
+ const positionedAnchor = positioned.find((node) => node.id === anchor.id);
10698
+ if (!positionedAnchor) return positioned;
10699
+ const offset = {
10700
+ x: anchor.position.x - positionedAnchor.position.x,
10701
+ y: anchor.position.y - positionedAnchor.position.y
10122
10702
  };
10123
- });
10124
- return positioned;
10125
- }, []);
10703
+ return positioned.map((node) => {
10704
+ if (node.parentId) return node;
10705
+ return {
10706
+ ...node,
10707
+ position: {
10708
+ x: node.position.x + offset.x,
10709
+ y: node.position.y + offset.y
10710
+ }
10711
+ };
10712
+ });
10713
+ },
10714
+ []
10715
+ );
10716
+ const makeRoomForRenderedExpandedGroup = (0, import_react79.useCallback)(
10717
+ (groupNodeId, groupBounds) => {
10718
+ const padding = 80;
10719
+ const protectedBounds = {
10720
+ x: groupBounds.x - padding,
10721
+ y: groupBounds.y - padding,
10722
+ width: groupBounds.width + padding * 2,
10723
+ height: groupBounds.height + padding * 2
10724
+ };
10725
+ const intersectingIds = new Set(
10726
+ getIntersectingNodes(protectedBounds, true).filter((node) => node.id !== groupNodeId && !node.parentId).map((node) => node.id)
10727
+ );
10728
+ if (intersectingIds.size === 0) return;
10729
+ setNodes((currentNodes) => {
10730
+ const plannedPositions = packNodesAroundBounds({
10731
+ nodes: currentNodes,
10732
+ movableNodeIds: intersectingIds,
10733
+ protectedBounds,
10734
+ groupNodeId
10735
+ });
10736
+ return currentNodes.map((node) => {
10737
+ const plannedPosition = plannedPositions.get(node.id);
10738
+ if (!plannedPosition) return node;
10739
+ return { ...node, position: plannedPosition };
10740
+ });
10741
+ });
10742
+ },
10743
+ [getIntersectingNodes, setNodes]
10744
+ );
10126
10745
  const layoutSubFlowChildren = (0, import_react79.useCallback)(
10127
10746
  (children, edges2, sizeOf, opts) => {
10128
10747
  const { padding, headerH, fallbackW = 240, fallbackH = 120 } = opts;
@@ -10220,9 +10839,19 @@ var NodeGraphBuilder = ({
10220
10839
  }
10221
10840
  const without = prev.filter(
10222
10841
  (n) => n.id !== groupNodeId && !childNodeIds.has(n.id) && !(downstreamNodeIds.has(n.id) && !referencedByEdges.has(n.id))
10223
- );
10224
- const next = [...without, originalNode];
10225
- return relayoutGraph(next, nextEdges);
10842
+ ).map((n) => {
10843
+ const stashedPosition = stashed?.nodePositions?.[n.id];
10844
+ if (!stashedPosition || n.parentId) return n;
10845
+ return { ...n, position: stashedPosition };
10846
+ });
10847
+ const next = [
10848
+ ...without,
10849
+ {
10850
+ ...originalNode,
10851
+ position: stashed?.nodePositions?.[groupNodeId] ?? expandedNode.position
10852
+ }
10853
+ ];
10854
+ return next;
10226
10855
  });
10227
10856
  setEdges((prev) => {
10228
10857
  const without = prev.filter(
@@ -10230,9 +10859,6 @@ var NodeGraphBuilder = ({
10230
10859
  );
10231
10860
  return [...without, ...originalEdges];
10232
10861
  });
10233
- requestAnimationFrame(() => {
10234
- fitView({ duration: 400, padding: 0.2 });
10235
- });
10236
10862
  },
10237
10863
  [
10238
10864
  initialNodes,
@@ -10240,8 +10866,7 @@ var NodeGraphBuilder = ({
10240
10866
  setNodes,
10241
10867
  setEdges,
10242
10868
  relayoutGraph,
10243
- animateLayout,
10244
- fitView
10869
+ animateLayout
10245
10870
  ]
10246
10871
  );
10247
10872
  (0, import_react79.useEffect)(() => {
@@ -10324,9 +10949,6 @@ var NodeGraphBuilder = ({
10324
10949
  onNodeClick(node);
10325
10950
  return;
10326
10951
  }
10327
- if (linksToVisualiser && onNavigate) {
10328
- return;
10329
- }
10330
10952
  const isFlow = edgesRef.current.some(
10331
10953
  (edge) => edge.type === "flow-edge"
10332
10954
  );
@@ -10340,6 +10962,24 @@ var NodeGraphBuilder = ({
10340
10962
  if (node.type === "messageGroup") {
10341
10963
  const groupData = node.data;
10342
10964
  const groupNodeId = node.id;
10965
+ const currentGroupNode = getExpandedMessageGroupNode(
10966
+ nodesRef.current,
10967
+ groupNodeId
10968
+ );
10969
+ if (currentGroupNode?.type === "messageGroupExpanded") {
10970
+ const measured = currentGroupNode?.measured;
10971
+ const width = measured?.width ?? currentGroupNode.style?.width ?? 380;
10972
+ const height = measured?.height ?? currentGroupNode.style?.height ?? 300;
10973
+ setCenter(
10974
+ currentGroupNode.position.x + width / 2,
10975
+ currentGroupNode.position.y + height / 2,
10976
+ {
10977
+ duration: 300,
10978
+ zoom: Math.min(Math.max(getZoom(), 0.55), 1)
10979
+ }
10980
+ );
10981
+ return;
10982
+ }
10343
10983
  const serviceNodeId = `${groupData.service.id}-${groupData.service.version}`;
10344
10984
  const childCount = groupData.messages?.length || 0;
10345
10985
  const containerWidth = 380;
@@ -10348,6 +10988,9 @@ var NodeGraphBuilder = ({
10348
10988
  (e) => e.source === groupNodeId || e.target === groupNodeId
10349
10989
  );
10350
10990
  const preExpansionNodeIds = nodesRef.current.map((n) => n.id);
10991
+ const preExpansionNodePositions = Object.fromEntries(
10992
+ nodesRef.current.map((n) => [n.id, { ...n.position }])
10993
+ );
10351
10994
  const expandedContainerNode = {
10352
10995
  id: groupNodeId,
10353
10996
  type: "messageGroupExpanded",
@@ -10360,7 +11003,8 @@ var NodeGraphBuilder = ({
10360
11003
  __preExpansion: {
10361
11004
  node,
10362
11005
  edges: preExpansionEdges,
10363
- nodeIds: preExpansionNodeIds
11006
+ nodeIds: preExpansionNodeIds,
11007
+ nodePositions: preExpansionNodePositions
10364
11008
  }
10365
11009
  },
10366
11010
  style: {
@@ -10421,24 +11065,19 @@ var NodeGraphBuilder = ({
10421
11065
  });
10422
11066
  animateLayout();
10423
11067
  setNodes((prev) => {
10424
- const without = prev.filter((n) => n.id !== groupNodeId);
10425
- const existingIds = new Set(without.map((n) => n.id));
10426
- const newDownstream = downstreamNodes.filter(
10427
- (n) => !existingIds.has(n.id)
10428
- );
10429
- const next = [
10430
- ...without,
11068
+ const downstreamX = groupData.direction === "sends" ? node.position.x + containerWidth + 260 : node.position.x - 420;
11069
+ const downstreamY = node.position.y + 40;
11070
+ return buildMessageGroupExpansionNodes({
11071
+ currentNodes: prev,
11072
+ groupNodeId,
10431
11073
  expandedContainerNode,
10432
- ...childNodes,
10433
- ...newDownstream
10434
- ];
10435
- return relayoutGraph(next, [
10436
- ...edgesRef.current.filter(
10437
- (e) => e.source !== groupNodeId && e.target !== groupNodeId
10438
- ),
10439
- ...childEdges,
10440
- ...downstreamEdges
10441
- ]);
11074
+ childNodes,
11075
+ downstreamNodes,
11076
+ getDownstreamPosition: (_downstreamNode, index) => ({
11077
+ x: downstreamX,
11078
+ y: downstreamY + index * 190
11079
+ })
11080
+ });
10442
11081
  });
10443
11082
  setEdges((prev) => {
10444
11083
  const without = prev.filter(
@@ -10447,6 +11086,12 @@ var NodeGraphBuilder = ({
10447
11086
  return [...without, ...childEdges, ...downstreamEdges];
10448
11087
  });
10449
11088
  requestAnimationFrame(() => {
11089
+ let actualContainerBounds = {
11090
+ x: node.position.x,
11091
+ y: node.position.y,
11092
+ width: containerWidth,
11093
+ height: containerHeight
11094
+ };
10450
11095
  setNodes((prev) => {
10451
11096
  const children = prev.filter((n) => n.parentId === groupNodeId);
10452
11097
  if (children.length === 0) return prev;
@@ -10466,8 +11111,20 @@ var NodeGraphBuilder = ({
10466
11111
  const totalChildH = measurements.reduce((sum, m) => sum + m.h, 0) + gap * (measurements.length - 1);
10467
11112
  const actualContainerH = headerH + totalChildH + padding * 2;
10468
11113
  let currentY = headerH + padding;
11114
+ actualContainerBounds = {
11115
+ x: node.position.x,
11116
+ y: node.position.y,
11117
+ width: containerWidth,
11118
+ height: actualContainerH
11119
+ };
10469
11120
  return prev.map((n) => {
10470
11121
  if (n.id === groupNodeId) {
11122
+ actualContainerBounds = {
11123
+ x: n.position.x,
11124
+ y: n.position.y,
11125
+ width: containerWidth,
11126
+ height: actualContainerH
11127
+ };
10471
11128
  return {
10472
11129
  ...n,
10473
11130
  style: {
@@ -10485,9 +11142,23 @@ var NodeGraphBuilder = ({
10485
11142
  return { ...n, position: { x, y } };
10486
11143
  });
10487
11144
  });
10488
- });
10489
- requestAnimationFrame(() => {
10490
- fitView({ duration: 400, padding: 0.2 });
11145
+ requestAnimationFrame(() => {
11146
+ const groupNode = getNodes().find((n) => n.id === groupNodeId);
11147
+ const measured = groupNode?.measured;
11148
+ const width = measured?.width ?? groupNode?.style?.width ?? containerWidth;
11149
+ const height = measured?.height ?? groupNode?.style?.height ?? actualContainerBounds.height;
11150
+ const bounds = {
11151
+ x: groupNode?.position.x ?? actualContainerBounds.x,
11152
+ y: groupNode?.position.y ?? actualContainerBounds.y,
11153
+ width,
11154
+ height
11155
+ };
11156
+ makeRoomForRenderedExpandedGroup(groupNodeId, bounds);
11157
+ setCenter(bounds.x + width / 2, bounds.y + height / 2, {
11158
+ duration: 450,
11159
+ zoom: Math.min(Math.max(getZoom(), 0.55), 1)
11160
+ });
11161
+ });
10491
11162
  });
10492
11163
  return;
10493
11164
  }
@@ -10652,10 +11323,21 @@ var NodeGraphBuilder = ({
10652
11323
  });
10653
11324
  return;
10654
11325
  }
11326
+ if (linksToVisualiser && onNavigate) {
11327
+ return;
11328
+ }
10655
11329
  setFocusedNodeId(node.id);
10656
11330
  setFocusModeOpen(true);
10657
11331
  },
10658
- [onNodeClick, linksToVisualiser, onNavigate]
11332
+ [
11333
+ onNodeClick,
11334
+ linksToVisualiser,
11335
+ onNavigate,
11336
+ makeRoomForRenderedExpandedGroup,
11337
+ getNodes,
11338
+ getZoom,
11339
+ setCenter
11340
+ ]
10659
11341
  );
10660
11342
  const toggleAnimateMessages = (0, import_react79.useCallback)(() => {
10661
11343
  setAnimateMessages((prev) => {
@@ -10885,13 +11567,15 @@ var NodeGraphBuilder = ({
10885
11567
  }, [getNodes, downloadImage, title]);
10886
11568
  const handleLegendClick = (0, import_react79.useCallback)(
10887
11569
  (collectionType, groupId) => {
11570
+ const isLegendTarget = (node) => {
11571
+ if (groupId) {
11572
+ return node.data.group && node.data.group?.id === groupId;
11573
+ }
11574
+ return node.type === collectionType;
11575
+ };
10888
11576
  const updatedNodes = nodes.map((node) => {
10889
- if (groupId && node.data.group && node.data.group?.id === groupId) {
11577
+ if (isLegendTarget(node)) {
10890
11578
  return { ...node, style: { ...node.style, opacity: 1 } };
10891
- } else {
10892
- if (node.type === collectionType) {
10893
- return { ...node, style: { ...node.style, opacity: 1 } };
10894
- }
10895
11579
  }
10896
11580
  return { ...node, style: { ...node.style, opacity: 0.1 } };
10897
11581
  });
@@ -10906,10 +11590,12 @@ var NodeGraphBuilder = ({
10906
11590
  });
10907
11591
  setNodes(updatedNodes);
10908
11592
  setEdges(updatedEdges);
11593
+ const targetNodes = updatedNodes.filter(isLegendTarget);
11594
+ if (targetNodes.length === 0) return;
10909
11595
  fitView({
10910
11596
  padding: 0.2,
10911
11597
  duration: 800,
10912
- nodes: updatedNodes.filter((node) => node.type === collectionType)
11598
+ nodes: targetNodes
10913
11599
  });
10914
11600
  },
10915
11601
  [nodes, edges, setNodes, setEdges, fitView]
@@ -11106,7 +11792,7 @@ var NodeGraphBuilder = ({
11106
11792
  "aria-label": "Open menu",
11107
11793
  children: [
11108
11794
  title && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "text-base font-medium text-[rgb(var(--ec-page-text))] leading-tight", children: title }),
11109
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react31.MoreVertical, { className: "h-5 w-5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0 group-hover:text-[rgb(var(--ec-accent))] transition-colors duration-150" })
11795
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react32.MoreVertical, { className: "h-5 w-5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0 group-hover:text-[rgb(var(--ec-accent))] transition-colors duration-150" })
11110
11796
  ]
11111
11797
  }
11112
11798
  ) }),
@@ -11208,7 +11894,7 @@ var NodeGraphBuilder = ({
11208
11894
  "aria-label": "Open menu",
11209
11895
  children: [
11210
11896
  title && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "text-base font-medium text-[rgb(var(--ec-page-text))] leading-tight", children: title }),
11211
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react31.MoreVertical, { className: "h-5 w-5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0 group-hover:text-[rgb(var(--ec-accent))] transition-colors duration-150" })
11897
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react32.MoreVertical, { className: "h-5 w-5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0 group-hover:text-[rgb(var(--ec-accent))] transition-colors duration-150" })
11212
11898
  ]
11213
11899
  }
11214
11900
  ) }),
@@ -11270,7 +11956,7 @@ var NodeGraphBuilder = ({
11270
11956
  ) }) })
11271
11957
  ] }),
11272
11958
  links.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "flex justify-end mt-3", children: /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "relative flex items-center -mt-1", children: [
11273
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "absolute left-2 pointer-events-none flex items-center h-full", children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react31.HistoryIcon, { className: "h-4 w-4 text-[rgb(var(--ec-page-text-muted))]" }) }),
11959
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "absolute left-2 pointer-events-none flex items-center h-full", children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react32.HistoryIcon, { className: "h-4 w-4 text-[rgb(var(--ec-page-text-muted))]" }) }),
11274
11960
  /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
11275
11961
  "select",
11276
11962
  {
@@ -11425,7 +12111,7 @@ var NodeGraphBuilder = ({
11425
12111
  onClick: () => setIsShareModalOpen(false),
11426
12112
  className: "text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))] transition-colors",
11427
12113
  "aria-label": "Close modal",
11428
- children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react31.ExternalLink, { className: "w-5 h-5 rotate-180" })
12114
+ children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react32.ExternalLink, { className: "w-5 h-5 rotate-180" })
11429
12115
  }
11430
12116
  )
11431
12117
  ] }),
@@ -11447,7 +12133,7 @@ var NodeGraphBuilder = ({
11447
12133
  className: `px-4 py-2.5 rounded-md font-medium transition-all duration-200 flex items-center gap-2 ${shareUrlCopySuccess ? "bg-green-500 text-white" : "bg-[rgb(var(--ec-accent))] text-white hover:opacity-90"}`,
11448
12134
  "aria-label": shareUrlCopySuccess ? "Copied!" : "Copy link",
11449
12135
  children: [
11450
- shareUrlCopySuccess ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react31.CheckIcon, { className: "w-4 h-4" }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react31.ClipboardIcon, { className: "w-4 h-4" }),
12136
+ shareUrlCopySuccess ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react32.CheckIcon, { className: "w-4 h-4" }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react32.ClipboardIcon, { className: "w-4 h-4" }),
11451
12137
  /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { children: shareUrlCopySuccess ? "Copied!" : "Copy" })
11452
12138
  ]
11453
12139
  }