@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.mjs CHANGED
@@ -5018,144 +5018,297 @@ var GroupNode_default = memo22(function GroupNode({ data }) {
5018
5018
  });
5019
5019
 
5020
5020
  // src/nodes/Custom.tsx
5021
- import { Handle as Handle17 } from "@xyflow/react";
5022
- import * as Icons from "@heroicons/react/24/solid";
5023
5021
  import { memo as memo23, useMemo as useMemo10 } from "react";
5022
+ import { Handle as Handle17, Position as Position15, useNodeConnections as useNodeConnections10 } from "@xyflow/react";
5023
+ import * as Icons from "@heroicons/react/24/solid";
5024
5024
  import * as ContextMenu4 from "@radix-ui/react-context-menu";
5025
- import * as Tooltip from "@radix-ui/react-tooltip";
5026
5025
  import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
5026
+ var PALETTES = {
5027
+ blue: {
5028
+ border: "border-blue-500",
5029
+ badge: "bg-blue-500",
5030
+ ring: "ring-2 ring-blue-400/60 ring-offset-2",
5031
+ shadow: "rgba(59, 130, 246, 0.15)",
5032
+ glow: "linear-gradient(135deg, #3b82f6, #2563eb)",
5033
+ pillBorder: "#3b82f6"
5034
+ },
5035
+ teal: {
5036
+ border: "border-teal-500",
5037
+ badge: "bg-teal-500",
5038
+ ring: "ring-2 ring-teal-400/60 ring-offset-2",
5039
+ shadow: "rgba(20, 184, 166, 0.15)",
5040
+ glow: "linear-gradient(135deg, #14b8a6, #0f766e)",
5041
+ pillBorder: "#14b8a6"
5042
+ },
5043
+ red: {
5044
+ border: "border-red-500",
5045
+ badge: "bg-red-500",
5046
+ ring: "ring-2 ring-red-400/60 ring-offset-2",
5047
+ shadow: "rgba(239, 68, 68, 0.15)",
5048
+ glow: "linear-gradient(135deg, #ef4444, #b91c1c)",
5049
+ pillBorder: "#ef4444"
5050
+ },
5051
+ green: {
5052
+ border: "border-green-500",
5053
+ badge: "bg-green-500",
5054
+ ring: "ring-2 ring-green-400/60 ring-offset-2",
5055
+ shadow: "rgba(34, 197, 94, 0.15)",
5056
+ glow: "linear-gradient(135deg, #22c55e, #15803d)",
5057
+ pillBorder: "#22c55e"
5058
+ },
5059
+ purple: {
5060
+ border: "border-purple-500",
5061
+ badge: "bg-purple-500",
5062
+ ring: "ring-2 ring-purple-400/60 ring-offset-2",
5063
+ shadow: "rgba(168, 85, 247, 0.15)",
5064
+ glow: "linear-gradient(135deg, #a855f7, #7e22ce)",
5065
+ pillBorder: "#a855f7"
5066
+ },
5067
+ orange: {
5068
+ border: "border-orange-500",
5069
+ badge: "bg-orange-500",
5070
+ ring: "ring-2 ring-orange-400/60 ring-offset-2",
5071
+ shadow: "rgba(251, 146, 60, 0.15)",
5072
+ glow: "linear-gradient(135deg, #fb923c, #ea580c)",
5073
+ pillBorder: "#fb923c"
5074
+ },
5075
+ pink: {
5076
+ border: "border-pink-500",
5077
+ badge: "bg-pink-500",
5078
+ ring: "ring-2 ring-pink-400/60 ring-offset-2",
5079
+ shadow: "rgba(236, 72, 153, 0.15)",
5080
+ glow: "linear-gradient(135deg, #ec4899, #be185d)",
5081
+ pillBorder: "#ec4899"
5082
+ },
5083
+ yellow: {
5084
+ border: "border-yellow-500",
5085
+ badge: "bg-yellow-500",
5086
+ ring: "ring-2 ring-yellow-400/60 ring-offset-2",
5087
+ shadow: "rgba(234, 179, 8, 0.15)",
5088
+ glow: "linear-gradient(135deg, #eab308, #a16207)",
5089
+ pillBorder: "#eab308"
5090
+ },
5091
+ gray: {
5092
+ border: "border-gray-500",
5093
+ badge: "bg-gray-500",
5094
+ ring: "ring-2 ring-gray-400/60 ring-offset-2",
5095
+ shadow: "rgba(107, 114, 128, 0.15)",
5096
+ glow: "linear-gradient(135deg, #6b7280, #374151)",
5097
+ pillBorder: "#6b7280"
5098
+ },
5099
+ indigo: {
5100
+ border: "border-indigo-500",
5101
+ badge: "bg-indigo-500",
5102
+ ring: "ring-2 ring-indigo-400/60 ring-offset-2",
5103
+ shadow: "rgba(99, 102, 241, 0.15)",
5104
+ glow: "linear-gradient(135deg, #6366f1, #4338ca)",
5105
+ pillBorder: "#6366f1"
5106
+ },
5107
+ cyan: {
5108
+ border: "border-cyan-500",
5109
+ badge: "bg-cyan-500",
5110
+ ring: "ring-2 ring-cyan-400/60 ring-offset-2",
5111
+ shadow: "rgba(6, 182, 212, 0.15)",
5112
+ glow: "linear-gradient(135deg, #06b6d4, #0e7490)",
5113
+ pillBorder: "#06b6d4"
5114
+ },
5115
+ slate: {
5116
+ border: "border-slate-500",
5117
+ badge: "bg-slate-500",
5118
+ ring: "ring-2 ring-slate-400/60 ring-offset-2",
5119
+ shadow: "rgba(100, 116, 139, 0.15)",
5120
+ glow: "linear-gradient(135deg, #64748b, #334155)",
5121
+ pillBorder: "#64748b"
5122
+ },
5123
+ amber: {
5124
+ border: "border-amber-500",
5125
+ badge: "bg-amber-500",
5126
+ ring: "ring-2 ring-amber-400/60 ring-offset-2",
5127
+ shadow: "rgba(245, 158, 11, 0.15)",
5128
+ glow: "linear-gradient(135deg, #f59e0b, #b45309)",
5129
+ pillBorder: "#f59e0b"
5130
+ },
5131
+ emerald: {
5132
+ border: "border-emerald-500",
5133
+ badge: "bg-emerald-500",
5134
+ ring: "ring-2 ring-emerald-400/60 ring-offset-2",
5135
+ shadow: "rgba(16, 185, 129, 0.15)",
5136
+ glow: "linear-gradient(135deg, #10b981, #047857)",
5137
+ pillBorder: "#10b981"
5138
+ },
5139
+ violet: {
5140
+ border: "border-violet-500",
5141
+ badge: "bg-violet-500",
5142
+ ring: "ring-2 ring-violet-400/60 ring-offset-2",
5143
+ shadow: "rgba(139, 92, 246, 0.15)",
5144
+ glow: "linear-gradient(135deg, #8b5cf6, #6d28d9)",
5145
+ pillBorder: "#8b5cf6"
5146
+ },
5147
+ rose: {
5148
+ border: "border-rose-500",
5149
+ badge: "bg-rose-500",
5150
+ ring: "ring-2 ring-rose-400/60 ring-offset-2",
5151
+ shadow: "rgba(244, 63, 94, 0.15)",
5152
+ glow: "linear-gradient(135deg, #f43f5e, #be123c)",
5153
+ pillBorder: "#f43f5e"
5154
+ }
5155
+ };
5027
5156
  function classNames16(...classes) {
5028
5157
  return classes.filter(Boolean).join(" ");
5029
5158
  }
5030
- var Custom_default = memo23(function UserNode2({
5031
- data,
5032
- sourcePosition,
5033
- targetPosition
5159
+ function getPalette(color) {
5160
+ return PALETTES[color || "blue"] ?? PALETTES.blue;
5161
+ }
5162
+ function GlowHandle14({
5163
+ side,
5164
+ gradient
5034
5165
  }) {
5035
- const { mode, step, custom: customProps } = data;
5166
+ return /* @__PURE__ */ jsx24(
5167
+ "div",
5168
+ {
5169
+ style: {
5170
+ position: "absolute",
5171
+ top: "50%",
5172
+ [side]: -6,
5173
+ transform: "translateY(-50%)",
5174
+ width: 12,
5175
+ height: 12,
5176
+ borderRadius: "50%",
5177
+ background: gradient,
5178
+ border: "2px solid rgb(var(--ec-page-bg))",
5179
+ zIndex: 20,
5180
+ animation: "ec-handle-pulse 2s ease-in-out infinite",
5181
+ pointerEvents: "none"
5182
+ }
5183
+ }
5184
+ );
5185
+ }
5186
+ var Custom_default = memo23(function CustomNode({ data, selected }) {
5187
+ const { mode, custom: customProps, step } = data;
5036
5188
  const {
5037
5189
  color = "blue",
5038
- title = "Custom",
5039
- icon = "UserIcon",
5040
- type = "custom",
5041
- summary = "",
5042
- url: _url = "",
5190
+ title = step?.title || "Custom",
5191
+ icon = "CubeIcon",
5192
+ type = "Custom",
5193
+ summary = step?.summary || "",
5194
+ url,
5043
5195
  properties = EMPTY_OBJECT,
5044
5196
  menu = EMPTY_ARRAY,
5045
5197
  height = 5
5046
5198
  } = customProps;
5047
- const IconComponent = useMemo10(() => Icons[icon], [icon]);
5048
- const { actor: { name: _name } = {} } = step;
5049
- const isLongType = type && type.length > 10;
5050
- const displayType = isLongType ? `${type.substring(0, 10)}...` : type;
5199
+ const palette = getPalette(color);
5200
+ const IconComponent = useMemo10(() => Icons[icon] || Icons.CubeIcon, [icon]);
5051
5201
  const portalContainer = usePortalContainer();
5202
+ const targetConnections = useNodeConnections10({ handleType: "target" });
5203
+ const sourceConnections = useNodeConnections10({ handleType: "source" });
5204
+ const propertyEntries = Object.entries(properties);
5205
+ const contextMenuItems = url ? [{ label: `Open ${type.toLowerCase()}`, url }, ...menu] : menu;
5052
5206
  return /* @__PURE__ */ jsxs22(ContextMenu4.Root, { children: [
5053
5207
  /* @__PURE__ */ jsx24(ContextMenu4.Trigger, { children: /* @__PURE__ */ jsxs22(
5054
5208
  "div",
5055
5209
  {
5056
5210
  className: classNames16(
5057
- `rounded-md border flex justify-start bg-[rgb(var(--ec-card-bg))] text-[rgb(var(--ec-page-text))] border-${color}-400`
5211
+ "relative min-w-48 max-w-60 rounded-xl border-2 overflow-visible",
5212
+ selected ? palette.ring : "",
5213
+ palette.border
5058
5214
  ),
5059
5215
  style: {
5060
- minHeight: mode === "full" ? `${height}em` : "2em",
5061
- ...NODE_WIDTH_STYLE
5216
+ background: "var(--ec-custom-node-bg, rgb(var(--ec-card-bg)))",
5217
+ boxShadow: `0 2px 12px ${palette.shadow}`,
5218
+ minHeight: mode === "full" ? `${height}em` : void 0
5062
5219
  },
5063
5220
  children: [
5064
- /* @__PURE__ */ jsxs22(
5065
- "div",
5221
+ /* @__PURE__ */ jsx24(
5222
+ Handle17,
5223
+ {
5224
+ type: "target",
5225
+ position: Position15.Left,
5226
+ style: HIDDEN_HANDLE_STYLE
5227
+ }
5228
+ ),
5229
+ /* @__PURE__ */ jsx24(
5230
+ Handle17,
5231
+ {
5232
+ type: "source",
5233
+ position: Position15.Right,
5234
+ style: HIDDEN_HANDLE_STYLE
5235
+ }
5236
+ ),
5237
+ targetConnections.length > 0 && /* @__PURE__ */ jsx24(GlowHandle14, { side: "left", gradient: palette.glow }),
5238
+ sourceConnections.length > 0 && /* @__PURE__ */ jsx24(GlowHandle14, { side: "right", gradient: palette.glow }),
5239
+ /* @__PURE__ */ jsx24("div", { className: "absolute -top-3 left-2.5 z-10", children: /* @__PURE__ */ jsxs22(
5240
+ "span",
5066
5241
  {
5067
5242
  className: classNames16(
5068
- `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`,
5069
- `border-r-[1px] border-${color}`
5243
+ "inline-flex items-center gap-1 text-[7px] font-bold uppercase tracking-widest text-white px-1.5 py-0.5 rounded shadow-sm",
5244
+ palette.badge
5070
5245
  ),
5071
5246
  children: [
5072
- IconComponent && /* @__PURE__ */ jsx24(IconComponent, { className: "w-4 h-4 opacity-90 text-white mt-1" }),
5073
- mode === "full" && /* @__PURE__ */ jsx24(Tooltip.Provider, { children: /* @__PURE__ */ jsxs22(Tooltip.Root, { children: [
5074
- /* @__PURE__ */ jsx24(Tooltip.Trigger, { asChild: true, children: /* @__PURE__ */ jsx24(
5075
- "span",
5076
- {
5077
- className: "text-center text-[9px] text-white font-bold uppercase mb-4",
5078
- style: ROTATED_LABEL_STYLE,
5079
- children: displayType
5080
- }
5081
- ) }),
5082
- isLongType && /* @__PURE__ */ jsx24(Tooltip.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs22(
5083
- Tooltip.Content,
5084
- {
5085
- className: "bg-slate-800 text-white rounded px-2 py-1 text-xs shadow-md z-50",
5086
- side: "right",
5087
- sideOffset: 5,
5088
- children: [
5089
- type,
5090
- /* @__PURE__ */ jsx24(Tooltip.Arrow, { className: "fill-slate-800" })
5091
- ]
5092
- }
5093
- ) })
5094
- ] }) })
5247
+ IconComponent && /* @__PURE__ */ jsx24(IconComponent, { className: "w-2.5 h-2.5", "aria-hidden": true }),
5248
+ type
5095
5249
  ]
5096
5250
  }
5097
- ),
5098
- /* @__PURE__ */ jsxs22("div", { className: "p-1 flex-1", children: [
5099
- targetPosition && /* @__PURE__ */ jsx24(Handle17, { type: "target", position: targetPosition }),
5100
- sourcePosition && /* @__PURE__ */ jsx24(Handle17, { type: "source", position: sourcePosition }),
5101
- (!summary || mode !== "full") && /* @__PURE__ */ jsx24("div", { className: "h-full ", children: /* @__PURE__ */ jsx24("span", { className: "text-sm font-bold block pb-0.5 w-full", children: title }) }),
5102
- summary && mode === "full" && /* @__PURE__ */ jsxs22("div", { children: [
5103
- /* @__PURE__ */ jsx24(
5251
+ ) }),
5252
+ /* @__PURE__ */ jsxs22("div", { className: "px-3 pt-3.5 pb-2.5", children: [
5253
+ /* @__PURE__ */ jsx24("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx24("span", { className: "text-[13px] font-semibold leading-snug text-[rgb(var(--ec-page-text))] truncate", children: title }) }),
5254
+ mode === "full" && summary && /* @__PURE__ */ jsx24(
5255
+ "div",
5256
+ {
5257
+ className: "mt-1.5 text-[9px] text-[rgb(var(--ec-page-text-muted))] leading-relaxed overflow-hidden",
5258
+ style: LINE_CLAMP_STYLE,
5259
+ title: summary,
5260
+ children: summary
5261
+ }
5262
+ ),
5263
+ mode === "full" && propertyEntries.length > 0 && /* @__PURE__ */ jsx24("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__ */ jsxs22("div", { className: "min-w-0", children: [
5264
+ /* @__PURE__ */ jsx24("div", { className: "text-[7px] font-bold uppercase tracking-wide text-[rgb(var(--ec-page-text-muted))] truncate", children: key }),
5265
+ typeof value === "string" && value.startsWith("http") ? /* @__PURE__ */ jsx24(
5266
+ "a",
5267
+ {
5268
+ href: value,
5269
+ target: "_blank",
5270
+ rel: "noopener noreferrer",
5271
+ className: "block text-[8px] text-blue-500 underline truncate",
5272
+ title: value,
5273
+ children: value
5274
+ }
5275
+ ) : /* @__PURE__ */ jsx24(
5104
5276
  "div",
5105
5277
  {
5106
- className: classNames16(
5107
- mode === "full" ? `border-b border-[rgb(var(--ec-page-border))]` : ""
5108
- ),
5109
- children: /* @__PURE__ */ jsx24("span", { className: "text-xs font-bold block pb-0.5", children: title })
5278
+ className: "text-[8px] text-[rgb(var(--ec-page-text))] truncate",
5279
+ title: String(value),
5280
+ children: value
5110
5281
  }
5111
- ),
5112
- mode === "full" && /* @__PURE__ */ jsxs22("div", { className: "divide-y divide-[rgb(var(--ec-page-border))] ", children: [
5113
- /* @__PURE__ */ jsx24("div", { className: "leading-3 py-1", children: /* @__PURE__ */ jsx24("span", { className: "text-[8px] font-light", children: summary }) }),
5114
- properties && /* @__PURE__ */ jsx24("div", { className: "grid grid-cols-2 gap-x-4 py-1", children: Object.entries(properties).map(([key, value]) => /* @__PURE__ */ jsxs22(
5115
- "span",
5116
- {
5117
- className: "text-xs",
5118
- style: TINY_FONT_STYLE,
5119
- children: [
5120
- key,
5121
- ":",
5122
- " ",
5123
- typeof value === "string" && value.startsWith("http") ? /* @__PURE__ */ jsx24(
5124
- "a",
5125
- {
5126
- href: value,
5127
- target: "_blank",
5128
- rel: "noopener noreferrer",
5129
- className: "text-blue-500 underline",
5130
- children: value
5131
- }
5132
- ) : value
5133
- ]
5134
- },
5135
- key
5136
- )) })
5137
- ] })
5138
- ] })
5282
+ )
5283
+ ] }, key)) })
5139
5284
  ] })
5140
5285
  ]
5141
5286
  }
5142
5287
  ) }),
5143
- menu?.length > 0 && /* @__PURE__ */ jsx24(ContextMenu4.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx24(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) => {
5144
- return /* @__PURE__ */ jsx24(
5145
- ContextMenu4.Item,
5146
- {
5147
- asChild: true,
5148
- className: "text-sm px-2 py-1.5 outline-none cursor-pointer hover:bg-orange-100 rounded-sm flex items-center",
5149
- children: /* @__PURE__ */ jsx24("a", { href: item.url, children: item.label })
5150
- }
5151
- );
5152
- }) }) })
5288
+ contextMenuItems.length > 0 && /* @__PURE__ */ jsx24(ContextMenu4.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx24(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__ */ jsx24(
5289
+ ContextMenu4.Item,
5290
+ {
5291
+ asChild: true,
5292
+ className: "text-sm px-2 py-1.5 outline-none cursor-pointer hover:bg-orange-100 rounded-sm flex items-center",
5293
+ children: /* @__PURE__ */ jsx24(
5294
+ "a",
5295
+ {
5296
+ href: item.url,
5297
+ target: "_blank",
5298
+ rel: "noopener noreferrer",
5299
+ className: "text-[rgb(var(--ec-page-text))] no-underline",
5300
+ children: item.label
5301
+ }
5302
+ )
5303
+ },
5304
+ `${item.label}-${item.url || ""}`
5305
+ )) }) })
5153
5306
  ] });
5154
5307
  });
5155
5308
 
5156
5309
  // src/nodes/ExternalSystem2.tsx
5157
5310
  import { memo as memo24 } from "react";
5158
- import { Handle as Handle18, Position as Position15 } from "@xyflow/react";
5311
+ import { Handle as Handle18, Position as Position16 } from "@xyflow/react";
5159
5312
  import { jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
5160
5313
  var ExternalSystem2_default = memo24(function ExternalSystemNode(props) {
5161
5314
  return /* @__PURE__ */ jsxs23("div", { className: "relative", children: [
@@ -5163,7 +5316,7 @@ var ExternalSystem2_default = memo24(function ExternalSystemNode(props) {
5163
5316
  Handle18,
5164
5317
  {
5165
5318
  type: "target",
5166
- position: Position15.Left,
5319
+ position: Position16.Left,
5167
5320
  style: EXTERNAL_SYSTEM_HANDLE_STYLE,
5168
5321
  className: "bg-gray-500"
5169
5322
  }
@@ -5172,7 +5325,7 @@ var ExternalSystem2_default = memo24(function ExternalSystemNode(props) {
5172
5325
  Handle18,
5173
5326
  {
5174
5327
  type: "source",
5175
- position: Position15.Right,
5328
+ position: Position16.Right,
5176
5329
  style: EXTERNAL_SYSTEM_HANDLE_STYLE,
5177
5330
  className: "bg-gray-500"
5178
5331
  }
@@ -5184,9 +5337,9 @@ var ExternalSystem2_default = memo24(function ExternalSystemNode(props) {
5184
5337
  // src/nodes/DataProduct.tsx
5185
5338
  import { memo as memo25 } from "react";
5186
5339
  import { Package } from "lucide-react";
5187
- import { Handle as Handle19, Position as Position16, useNodeConnections as useNodeConnections10 } from "@xyflow/react";
5340
+ import { Handle as Handle19, Position as Position17, useNodeConnections as useNodeConnections11 } from "@xyflow/react";
5188
5341
  import { jsx as jsx26, jsxs as jsxs24 } from "react/jsx-runtime";
5189
- function GlowHandle14({ side }) {
5342
+ function GlowHandle15({ side }) {
5190
5343
  return /* @__PURE__ */ jsx26(
5191
5344
  "div",
5192
5345
  {
@@ -5226,7 +5379,7 @@ function PostItDataProduct(props) {
5226
5379
  Handle19,
5227
5380
  {
5228
5381
  type: "target",
5229
- position: Position16.Left,
5382
+ position: Position17.Left,
5230
5383
  style: HIDDEN_HANDLE_STYLE
5231
5384
  }
5232
5385
  ),
@@ -5234,7 +5387,7 @@ function PostItDataProduct(props) {
5234
5387
  Handle19,
5235
5388
  {
5236
5389
  type: "source",
5237
- position: Position16.Right,
5390
+ position: Position17.Right,
5238
5391
  style: HIDDEN_HANDLE_STYLE
5239
5392
  }
5240
5393
  ),
@@ -5321,8 +5474,8 @@ function DefaultDataProduct(props) {
5321
5474
  const { version, name, summary, deprecated, draft, notes, styles } = props.data.dataProduct;
5322
5475
  const mode = props.data.mode || "simple";
5323
5476
  const customIcon = isIconPath(styles?.icon) ? styles.icon : void 0;
5324
- const targetConnections = useNodeConnections10({ handleType: "target" });
5325
- const sourceConnections = useNodeConnections10({ handleType: "source" });
5477
+ const targetConnections = useNodeConnections11({ handleType: "target" });
5478
+ const sourceConnections = useNodeConnections11({ handleType: "source" });
5326
5479
  const isDark = useDarkMode();
5327
5480
  const deprecatedStripe = isDark ? "rgba(239,68,68,0.25)" : "rgba(239,68,68,0.1)";
5328
5481
  return /* @__PURE__ */ jsxs24(
@@ -5342,7 +5495,7 @@ function DefaultDataProduct(props) {
5342
5495
  Handle19,
5343
5496
  {
5344
5497
  type: "target",
5345
- position: Position16.Left,
5498
+ position: Position17.Left,
5346
5499
  style: HIDDEN_HANDLE_STYLE
5347
5500
  }
5348
5501
  ),
@@ -5350,13 +5503,13 @@ function DefaultDataProduct(props) {
5350
5503
  Handle19,
5351
5504
  {
5352
5505
  type: "source",
5353
- position: Position16.Right,
5506
+ position: Position17.Right,
5354
5507
  style: HIDDEN_HANDLE_STYLE
5355
5508
  }
5356
5509
  ),
5357
5510
  notes && notes.length > 0 && /* @__PURE__ */ jsx26(NotesIndicator, { notes, resourceName: name }),
5358
- targetConnections.length > 0 && /* @__PURE__ */ jsx26(GlowHandle14, { side: "left" }),
5359
- sourceConnections.length > 0 && /* @__PURE__ */ jsx26(GlowHandle14, { side: "right" }),
5511
+ targetConnections.length > 0 && /* @__PURE__ */ jsx26(GlowHandle15, { side: "left" }),
5512
+ sourceConnections.length > 0 && /* @__PURE__ */ jsx26(GlowHandle15, { side: "right" }),
5360
5513
  /* @__PURE__ */ jsx26("div", { className: "absolute -top-2.5 left-2.5 flex items-center gap-1.5 z-10", children: /* @__PURE__ */ jsxs24(
5361
5514
  "span",
5362
5515
  {
@@ -5416,7 +5569,7 @@ var DataProduct_default = memo25(function DataProductNode(props) {
5416
5569
  // src/nodes/message-group/MessageGroupNode.tsx
5417
5570
  import { memo as memo26, useMemo as useMemo11 } from "react";
5418
5571
  import { Layers, Zap as Zap3, Terminal, HelpCircle, Maximize2 as Maximize22 } from "lucide-react";
5419
- import { Handle as Handle20, Position as Position17, useNodeConnections as useNodeConnections11 } from "@xyflow/react";
5572
+ import { Handle as Handle20, Position as Position18, useNodeConnections as useNodeConnections12 } from "@xyflow/react";
5420
5573
  import { jsx as jsx27, jsxs as jsxs25 } from "react/jsx-runtime";
5421
5574
  var TYPE_ICONS = {
5422
5575
  events: Zap3,
@@ -5432,8 +5585,8 @@ var CARD_STYLE = {
5432
5585
  var MessageGroupNode_default = memo26(function MessageGroupNode(props) {
5433
5586
  const { groupName, messageCount } = props.data;
5434
5587
  const isDark = useDarkMode();
5435
- const targetConnections = useNodeConnections11({ handleType: "target" });
5436
- const sourceConnections = useNodeConnections11({ handleType: "source" });
5588
+ const targetConnections = useNodeConnections12({ handleType: "target" });
5589
+ const sourceConnections = useNodeConnections12({ handleType: "source" });
5437
5590
  const stackDepth = messageCount >= 3 ? 3 : messageCount;
5438
5591
  const typeBreakdown = useMemo11(() => {
5439
5592
  const counts = {};
@@ -5481,7 +5634,7 @@ var MessageGroupNode_default = memo26(function MessageGroupNode(props) {
5481
5634
  Handle20,
5482
5635
  {
5483
5636
  type: "target",
5484
- position: Position17.Left,
5637
+ position: Position18.Left,
5485
5638
  style: HIDDEN_HANDLE_STYLE
5486
5639
  }
5487
5640
  ),
@@ -5489,7 +5642,7 @@ var MessageGroupNode_default = memo26(function MessageGroupNode(props) {
5489
5642
  Handle20,
5490
5643
  {
5491
5644
  type: "source",
5492
- position: Position17.Right,
5645
+ position: Position18.Right,
5493
5646
  style: HIDDEN_HANDLE_STYLE
5494
5647
  }
5495
5648
  ),
@@ -6043,7 +6196,75 @@ import {
6043
6196
  useImperativeHandle,
6044
6197
  memo as memo31
6045
6198
  } from "react";
6199
+ import {
6200
+ Blocks,
6201
+ Database as Database5,
6202
+ Layers as Layers3,
6203
+ ListTree,
6204
+ MessageSquare as MessageSquare2,
6205
+ Search as SearchIcon,
6206
+ Server as Server2,
6207
+ User as User3,
6208
+ Workflow as Workflow2,
6209
+ Zap as Zap4
6210
+ } from "lucide-react";
6046
6211
  import { jsx as jsx32, jsxs as jsxs30 } from "react/jsx-runtime";
6212
+ var formatVersionedName = (name, version) => {
6213
+ if (version) {
6214
+ const nameWithoutVersion = name.replace(
6215
+ new RegExp(`-v?${version.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`),
6216
+ ""
6217
+ );
6218
+ return `${nameWithoutVersion} (v${version})`;
6219
+ }
6220
+ const versionMatch = name.match(
6221
+ /^(.+)-v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)$/
6222
+ );
6223
+ if (!versionMatch) return name;
6224
+ return `${versionMatch[1]} (v${versionMatch[2]})`;
6225
+ };
6226
+ var normalizeCollectionType = (type) => {
6227
+ const aliases = {
6228
+ event: "events",
6229
+ command: "commands",
6230
+ query: "queries",
6231
+ service: "services",
6232
+ domain: "domains",
6233
+ channel: "channels",
6234
+ entity: "entities"
6235
+ };
6236
+ return aliases[type] || type;
6237
+ };
6238
+ var splitVersionedName = (name, version) => {
6239
+ if (version) {
6240
+ return {
6241
+ id: name.replace(
6242
+ new RegExp(`-v?${version.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`),
6243
+ ""
6244
+ ),
6245
+ version
6246
+ };
6247
+ }
6248
+ const versionMatch = name.match(
6249
+ /^(.+)-v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)$/
6250
+ );
6251
+ if (!versionMatch) return { id: name, version: void 0 };
6252
+ return { id: versionMatch[1], version: versionMatch[2] };
6253
+ };
6254
+ var getResourceKey = ({
6255
+ type,
6256
+ id,
6257
+ name,
6258
+ version
6259
+ }) => {
6260
+ const parsed = splitVersionedName(id || name, version);
6261
+ return `${normalizeCollectionType(type)}:${parsed.id}:${parsed.version || ""}`.toLowerCase();
6262
+ };
6263
+ var getNodeResourceData = (data, key) => {
6264
+ const resource = data?.[key];
6265
+ if (!resource || typeof resource !== "object") return void 0;
6266
+ return "data" in resource && resource.data ? resource.data : resource;
6267
+ };
6047
6268
  var VisualiserSearch = memo31(
6048
6269
  forwardRef(
6049
6270
  ({ nodes, onNodeSelect, onClear, onPaneClick: _onPaneClick }, ref) => {
@@ -6053,6 +6274,8 @@ var VisualiserSearch = memo31(
6053
6274
  const [selectedSuggestionIndex, setSelectedSuggestionIndex] = useState5(-1);
6054
6275
  const searchInputRef = useRef2(null);
6055
6276
  const containerRef = useRef2(null);
6277
+ const suggestionsListRef = useRef2(null);
6278
+ const suggestionItemRefs = useRef2([]);
6056
6279
  const hideSuggestions = useCallback4(() => {
6057
6280
  setShowSuggestions(false);
6058
6281
  setSelectedSuggestionIndex(-1);
@@ -6071,84 +6294,277 @@ var VisualiserSearch = memo31(
6071
6294
  if (node.type === "messageGroupExpanded") {
6072
6295
  return node.data?.groupName || node.id;
6073
6296
  }
6074
- 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;
6075
- 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;
6076
- return version ? `${name} (v${version})` : name;
6297
+ const message = getNodeResourceData(node.data, "message");
6298
+ const service = getNodeResourceData(node.data, "service");
6299
+ const domain = getNodeResourceData(node.data, "domain");
6300
+ const entity = getNodeResourceData(node.data, "entity");
6301
+ const channel = getNodeResourceData(node.data, "channel");
6302
+ const dataProduct = getNodeResourceData(node.data, "dataProduct");
6303
+ const data = getNodeResourceData(node.data, "data");
6304
+ 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;
6305
+ const version = message?.version || service?.version || domain?.version || entity?.version || channel?.version || dataProduct?.version || data?.version || node.data?.version;
6306
+ return formatVersionedName(name, version);
6077
6307
  }, []);
6078
- const getNodeTypeColorClass = useCallback4((nodeType) => {
6079
- const colorClasses = {
6080
- events: "bg-orange-600 text-white",
6081
- services: "bg-pink-600 text-white",
6082
- flows: "bg-teal-600 text-white",
6083
- commands: "bg-blue-600 text-white",
6084
- queries: "bg-green-600 text-white",
6085
- channels: "bg-gray-600 text-white",
6086
- domains: "bg-yellow-500 text-white",
6087
- externalSystem: "bg-pink-600 text-white",
6088
- actor: "bg-yellow-500 text-white",
6089
- step: "bg-gray-700 text-white",
6090
- user: "bg-yellow-500 text-white",
6091
- custom: "bg-gray-500 text-white",
6092
- field: "bg-cyan-600 text-white",
6093
- messageGroup: "bg-violet-600 text-white",
6094
- messageGroupExpanded: "bg-violet-600 text-white"
6308
+ const getNodeResourceKey = useCallback4(
6309
+ (node, label) => {
6310
+ if (node.type === "messageGroup" || node.type === "messageGroupExpanded") {
6311
+ return `${node.type}:${node.id}`.toLowerCase();
6312
+ }
6313
+ const data = node.data;
6314
+ const resource = getNodeResourceData(data, "message") || getNodeResourceData(data, "service") || getNodeResourceData(data, "domain") || getNodeResourceData(data, "entity") || getNodeResourceData(data, "channel") || getNodeResourceData(data, "dataProduct") || data?.data || data;
6315
+ return getResourceKey({
6316
+ type: node.type || "unknown",
6317
+ id: resource?.id,
6318
+ name: resource?.name || resource?.id || label || node.id,
6319
+ version: resource?.version || data?.version
6320
+ });
6321
+ },
6322
+ []
6323
+ );
6324
+ const dedupeSearchSuggestions = useCallback4(
6325
+ (suggestions) => {
6326
+ const uniqueSuggestions = [];
6327
+ const indexByResourceKey = /* @__PURE__ */ new Map();
6328
+ suggestions.forEach((suggestion) => {
6329
+ const existingIndex = indexByResourceKey.get(
6330
+ suggestion.resourceKey
6331
+ );
6332
+ if (existingIndex === void 0) {
6333
+ indexByResourceKey.set(
6334
+ suggestion.resourceKey,
6335
+ uniqueSuggestions.length
6336
+ );
6337
+ uniqueSuggestions.push(suggestion);
6338
+ return;
6339
+ }
6340
+ if (suggestion.isGroupedMessage && !uniqueSuggestions[existingIndex].isGroupedMessage) {
6341
+ uniqueSuggestions[existingIndex] = suggestion;
6342
+ }
6343
+ });
6344
+ return uniqueSuggestions;
6345
+ },
6346
+ []
6347
+ );
6348
+ const getSearchSuggestions = useCallback4(
6349
+ (nodesToIndex) => {
6350
+ const suggestions = nodesToIndex.flatMap((node) => {
6351
+ const nodeName = getNodeDisplayName(node);
6352
+ const suggestions2 = [
6353
+ {
6354
+ key: node.id,
6355
+ node,
6356
+ label: nodeName,
6357
+ searchText: nodeName,
6358
+ type: node.type || "unknown",
6359
+ resourceKey: getNodeResourceKey(node, nodeName)
6360
+ }
6361
+ ];
6362
+ if (node.type !== "messageGroup") return suggestions2;
6363
+ const groupName = node.data?.groupName || nodeName;
6364
+ const groupedMessages = node.data?.messages || [];
6365
+ groupedMessages.forEach((item, index) => {
6366
+ const message = item.message;
6367
+ const messageName = message?.data?.name || message?.data?.id;
6368
+ if (!messageName) return;
6369
+ const version = message?.data?.version;
6370
+ const label = formatVersionedName(messageName, version);
6371
+ suggestions2.push({
6372
+ key: `${node.id}:${message?.data?.id || messageName}:${version || index}`,
6373
+ node,
6374
+ label,
6375
+ searchText: `${label} ${groupName}`,
6376
+ type: message?.collection || "message",
6377
+ resourceKey: getResourceKey({
6378
+ type: message?.collection || "message",
6379
+ id: message?.data?.id,
6380
+ name: messageName,
6381
+ version
6382
+ }),
6383
+ groupName,
6384
+ isGroupedMessage: true
6385
+ });
6386
+ });
6387
+ return suggestions2;
6388
+ });
6389
+ return dedupeSearchSuggestions(suggestions);
6390
+ },
6391
+ [dedupeSearchSuggestions, getNodeDisplayName, getNodeResourceKey]
6392
+ );
6393
+ const getNodeTypeMeta = useCallback4((nodeType) => {
6394
+ const meta = {
6395
+ events: {
6396
+ label: "Event",
6397
+ Icon: Zap4,
6398
+ iconClass: "border-orange-500/25 bg-orange-500/10 text-orange-500",
6399
+ badgeClass: "border-orange-500/25 bg-orange-500/10 text-orange-700 dark:text-orange-300"
6400
+ },
6401
+ event: {
6402
+ label: "Event",
6403
+ Icon: Zap4,
6404
+ iconClass: "border-orange-500/25 bg-orange-500/10 text-orange-500",
6405
+ badgeClass: "border-orange-500/25 bg-orange-500/10 text-orange-700 dark:text-orange-300"
6406
+ },
6407
+ commands: {
6408
+ label: "Command",
6409
+ Icon: MessageSquare2,
6410
+ iconClass: "border-blue-500/25 bg-blue-500/10 text-blue-500",
6411
+ badgeClass: "border-blue-500/25 bg-blue-500/10 text-blue-700 dark:text-blue-300"
6412
+ },
6413
+ command: {
6414
+ label: "Command",
6415
+ Icon: MessageSquare2,
6416
+ iconClass: "border-blue-500/25 bg-blue-500/10 text-blue-500",
6417
+ badgeClass: "border-blue-500/25 bg-blue-500/10 text-blue-700 dark:text-blue-300"
6418
+ },
6419
+ queries: {
6420
+ label: "Query",
6421
+ Icon: SearchIcon,
6422
+ iconClass: "border-green-500/25 bg-green-500/10 text-green-500",
6423
+ badgeClass: "border-green-500/25 bg-green-500/10 text-green-700 dark:text-green-300"
6424
+ },
6425
+ query: {
6426
+ label: "Query",
6427
+ Icon: SearchIcon,
6428
+ iconClass: "border-green-500/25 bg-green-500/10 text-green-500",
6429
+ badgeClass: "border-green-500/25 bg-green-500/10 text-green-700 dark:text-green-300"
6430
+ },
6431
+ services: {
6432
+ label: "Service",
6433
+ Icon: Server2,
6434
+ iconClass: "border-pink-500/25 bg-pink-500/10 text-pink-500",
6435
+ badgeClass: "border-pink-500/25 bg-pink-500/10 text-pink-700 dark:text-pink-300"
6436
+ },
6437
+ domains: {
6438
+ label: "Domain",
6439
+ Icon: Blocks,
6440
+ iconClass: "border-yellow-500/25 bg-yellow-500/10 text-yellow-600 dark:text-yellow-400",
6441
+ badgeClass: "border-yellow-500/25 bg-yellow-500/10 text-yellow-700 dark:text-yellow-300"
6442
+ },
6443
+ flows: {
6444
+ label: "Flow",
6445
+ Icon: Workflow2,
6446
+ iconClass: "border-teal-500/25 bg-teal-500/10 text-teal-500",
6447
+ badgeClass: "border-teal-500/25 bg-teal-500/10 text-teal-700 dark:text-teal-300"
6448
+ },
6449
+ channels: {
6450
+ label: "Channel",
6451
+ Icon: ListTree,
6452
+ iconClass: "border-gray-500/25 bg-gray-500/10 text-gray-500",
6453
+ badgeClass: "border-gray-500/25 bg-gray-500/10 text-gray-700 dark:text-gray-300"
6454
+ },
6455
+ data: {
6456
+ label: "Data",
6457
+ Icon: Database5,
6458
+ iconClass: "border-blue-500/25 bg-blue-500/10 text-blue-500",
6459
+ badgeClass: "border-blue-500/25 bg-blue-500/10 text-blue-700 dark:text-blue-300"
6460
+ },
6461
+ entities: {
6462
+ label: "Entity",
6463
+ Icon: Database5,
6464
+ iconClass: "border-blue-500/25 bg-blue-500/10 text-blue-500",
6465
+ badgeClass: "border-blue-500/25 bg-blue-500/10 text-blue-700 dark:text-blue-300"
6466
+ },
6467
+ externalSystem: {
6468
+ label: "External",
6469
+ Icon: Server2,
6470
+ iconClass: "border-pink-500/25 bg-pink-500/10 text-pink-500",
6471
+ badgeClass: "border-pink-500/25 bg-pink-500/10 text-pink-700 dark:text-pink-300"
6472
+ },
6473
+ actor: {
6474
+ label: "Actor",
6475
+ Icon: User3,
6476
+ iconClass: "border-yellow-500/25 bg-yellow-500/10 text-yellow-600 dark:text-yellow-400",
6477
+ badgeClass: "border-yellow-500/25 bg-yellow-500/10 text-yellow-700 dark:text-yellow-300"
6478
+ },
6479
+ user: {
6480
+ label: "User",
6481
+ Icon: User3,
6482
+ iconClass: "border-yellow-500/25 bg-yellow-500/10 text-yellow-600 dark:text-yellow-400",
6483
+ badgeClass: "border-yellow-500/25 bg-yellow-500/10 text-yellow-700 dark:text-yellow-300"
6484
+ },
6485
+ messageGroup: {
6486
+ label: "Group",
6487
+ Icon: Layers3,
6488
+ iconClass: "border-violet-500/25 bg-violet-500/10 text-violet-500",
6489
+ badgeClass: "border-violet-500/25 bg-violet-500/10 text-violet-700 dark:text-violet-300"
6490
+ },
6491
+ messageGroupExpanded: {
6492
+ label: "Group",
6493
+ Icon: Layers3,
6494
+ iconClass: "border-violet-500/25 bg-violet-500/10 text-violet-500",
6495
+ badgeClass: "border-violet-500/25 bg-violet-500/10 text-violet-700 dark:text-violet-300"
6496
+ }
6497
+ };
6498
+ return meta[nodeType] || {
6499
+ label: nodeType,
6500
+ Icon: Layers3,
6501
+ iconClass: "border-gray-500/25 bg-gray-500/10 text-gray-500",
6502
+ badgeClass: "border-gray-500/25 bg-gray-500/10 text-gray-700 dark:text-gray-300"
6095
6503
  };
6096
- return colorClasses[nodeType] || "bg-gray-100 text-gray-700";
6097
6504
  }, []);
6098
6505
  const handleSearchChange = useCallback4(
6099
6506
  (event) => {
6100
6507
  const query = event.target.value;
6101
6508
  setSearchQuery(query);
6102
6509
  if (query.length > 0) {
6103
- const filtered = nodes.filter((node) => {
6104
- const nodeName = getNodeDisplayName(node);
6105
- return nodeName.toLowerCase().includes(query.toLowerCase());
6106
- });
6510
+ const search = query.toLowerCase();
6511
+ const filtered = getSearchSuggestions(nodes).filter(
6512
+ (suggestion) => suggestion.searchText.toLowerCase().includes(search)
6513
+ );
6107
6514
  setFilteredSuggestions(filtered);
6108
6515
  setShowSuggestions(true);
6109
6516
  setSelectedSuggestionIndex(-1);
6110
6517
  } else {
6111
- setFilteredSuggestions(nodes);
6518
+ setFilteredSuggestions(getSearchSuggestions(nodes));
6112
6519
  setShowSuggestions(true);
6113
6520
  setSelectedSuggestionIndex(-1);
6114
6521
  }
6115
6522
  },
6116
- [nodes, getNodeDisplayName]
6523
+ [nodes, getSearchSuggestions]
6117
6524
  );
6118
6525
  const handleSearchFocus = useCallback4(() => {
6119
- if (searchQuery.length === 0) {
6120
- setFilteredSuggestions(nodes);
6121
- }
6526
+ const suggestions = getSearchSuggestions(nodes);
6527
+ const search = searchQuery.toLowerCase();
6528
+ setFilteredSuggestions(
6529
+ searchQuery.length === 0 ? suggestions : suggestions.filter(
6530
+ (suggestion) => suggestion.searchText.toLowerCase().includes(search)
6531
+ )
6532
+ );
6122
6533
  setShowSuggestions(true);
6123
6534
  setSelectedSuggestionIndex(-1);
6124
- }, [nodes, searchQuery]);
6535
+ }, [nodes, searchQuery, getSearchSuggestions]);
6125
6536
  const handleSuggestionClick = useCallback4(
6126
- (node) => {
6127
- setSearchQuery(getNodeDisplayName(node));
6537
+ (suggestion) => {
6538
+ setSearchQuery("");
6539
+ setFilteredSuggestions([]);
6128
6540
  setShowSuggestions(false);
6129
- onNodeSelect(node);
6541
+ setSelectedSuggestionIndex(-1);
6542
+ onNodeSelect(suggestion.node);
6130
6543
  },
6131
- [onNodeSelect, getNodeDisplayName]
6544
+ [onNodeSelect]
6132
6545
  );
6133
6546
  const handleSearchKeyDown = useCallback4(
6134
6547
  (event) => {
6135
- if (!showSuggestions || filteredSuggestions.length === 0) return;
6136
6548
  switch (event.key) {
6137
6549
  case "ArrowDown":
6138
6550
  event.preventDefault();
6551
+ if (filteredSuggestions.length === 0) return;
6552
+ setShowSuggestions(true);
6139
6553
  setSelectedSuggestionIndex(
6140
6554
  (prev) => prev < filteredSuggestions.length - 1 ? prev + 1 : 0
6141
6555
  );
6142
6556
  break;
6143
6557
  case "ArrowUp":
6144
6558
  event.preventDefault();
6559
+ if (filteredSuggestions.length === 0) return;
6560
+ setShowSuggestions(true);
6145
6561
  setSelectedSuggestionIndex(
6146
6562
  (prev) => prev > 0 ? prev - 1 : filteredSuggestions.length - 1
6147
6563
  );
6148
6564
  break;
6149
6565
  case "Enter":
6150
6566
  event.preventDefault();
6151
- if (selectedSuggestionIndex >= 0) {
6567
+ if (showSuggestions && selectedSuggestionIndex >= 0 && selectedSuggestionIndex < filteredSuggestions.length) {
6152
6568
  handleSuggestionClick(
6153
6569
  filteredSuggestions[selectedSuggestionIndex]
6154
6570
  );
@@ -6177,6 +6593,31 @@ var VisualiserSearch = memo31(
6177
6593
  searchInputRef.current.focus();
6178
6594
  }
6179
6595
  }, [onClear]);
6596
+ useEffect2(() => {
6597
+ suggestionItemRefs.current = suggestionItemRefs.current.slice(
6598
+ 0,
6599
+ filteredSuggestions.length
6600
+ );
6601
+ }, [filteredSuggestions.length]);
6602
+ useEffect2(() => {
6603
+ if (!showSuggestions || selectedSuggestionIndex < 0) return;
6604
+ const list = suggestionsListRef.current;
6605
+ const item = suggestionItemRefs.current[selectedSuggestionIndex];
6606
+ if (!list || !item) return;
6607
+ const itemTop = item.offsetTop;
6608
+ const itemBottom = itemTop + item.offsetHeight;
6609
+ const visibleTop = list.scrollTop;
6610
+ const visibleBottom = visibleTop + list.clientHeight;
6611
+ if (itemTop < visibleTop) {
6612
+ list.scrollTop = itemTop;
6613
+ } else if (itemBottom > visibleBottom) {
6614
+ list.scrollTop = itemBottom - list.clientHeight;
6615
+ }
6616
+ }, [
6617
+ showSuggestions,
6618
+ selectedSuggestionIndex,
6619
+ filteredSuggestions.length
6620
+ ]);
6180
6621
  useEffect2(() => {
6181
6622
  const handleClickOutside = (event) => {
6182
6623
  if (containerRef.current && !containerRef.current.contains(event.target)) {
@@ -6231,28 +6672,53 @@ var VisualiserSearch = memo31(
6231
6672
  }
6232
6673
  )
6233
6674
  ] }),
6234
- showSuggestions && filteredSuggestions.length > 0 && /* @__PURE__ */ jsx32("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) => {
6235
- const nodeName = getNodeDisplayName(node);
6236
- const nodeType = node.type || "unknown";
6237
- return /* @__PURE__ */ jsxs30(
6238
- "div",
6239
- {
6240
- onClick: () => handleSuggestionClick(node),
6241
- 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))]" : ""}`,
6242
- children: [
6243
- /* @__PURE__ */ jsx32("span", { className: "text-sm font-medium text-[rgb(var(--ec-page-text))]", children: nodeName }),
6244
- /* @__PURE__ */ jsx32(
6245
- "span",
6246
- {
6247
- className: `text-xs capitalize px-2 py-1 rounded ${getNodeTypeColorClass(nodeType)}`,
6248
- children: nodeType
6249
- }
6250
- )
6251
- ]
6252
- },
6253
- node.id
6254
- );
6255
- }) })
6675
+ showSuggestions && filteredSuggestions.length > 0 && /* @__PURE__ */ jsx32(
6676
+ "div",
6677
+ {
6678
+ ref: suggestionsListRef,
6679
+ 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",
6680
+ children: filteredSuggestions.map((suggestion, index) => {
6681
+ const nodeTypeMeta = getNodeTypeMeta(suggestion.type);
6682
+ const Icon = nodeTypeMeta.Icon;
6683
+ const isSelected = index === selectedSuggestionIndex;
6684
+ return /* @__PURE__ */ jsxs30(
6685
+ "div",
6686
+ {
6687
+ ref: (element) => {
6688
+ suggestionItemRefs.current[index] = element;
6689
+ },
6690
+ onClick: () => handleSuggestionClick(suggestion),
6691
+ onMouseEnter: () => setSelectedSuggestionIndex(index),
6692
+ 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)]"}`,
6693
+ children: [
6694
+ /* @__PURE__ */ jsx32(
6695
+ "span",
6696
+ {
6697
+ className: `mt-0.5 flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md border ${nodeTypeMeta.iconClass}`,
6698
+ children: /* @__PURE__ */ jsx32(Icon, { className: "h-3.5 w-3.5" })
6699
+ }
6700
+ ),
6701
+ /* @__PURE__ */ jsxs30("span", { className: "min-w-0 flex-1", children: [
6702
+ /* @__PURE__ */ jsx32("span", { className: "block truncate text-sm font-medium text-[rgb(var(--ec-page-text))]", children: suggestion.label }),
6703
+ suggestion.isGroupedMessage && suggestion.groupName && /* @__PURE__ */ jsxs30("span", { className: "mt-0.5 block truncate text-xs text-[rgb(var(--ec-page-text-muted))]", children: [
6704
+ "in ",
6705
+ suggestion.groupName
6706
+ ] })
6707
+ ] }),
6708
+ /* @__PURE__ */ jsx32(
6709
+ "span",
6710
+ {
6711
+ className: `mt-0.5 flex-shrink-0 rounded border px-2 py-0.5 text-xs font-medium ${nodeTypeMeta.badgeClass}`,
6712
+ children: nodeTypeMeta.label
6713
+ }
6714
+ )
6715
+ ]
6716
+ },
6717
+ `${suggestion.key}:${index}`
6718
+ );
6719
+ })
6720
+ }
6721
+ )
6256
6722
  ] });
6257
6723
  }
6258
6724
  )
@@ -6810,7 +7276,7 @@ function getNodeDocUrl(node) {
6810
7276
  }
6811
7277
 
6812
7278
  // src/components/FocusMode/FocusModeNodeActions.tsx
6813
- import { NodeToolbar, Position as Position18, useViewport } from "@xyflow/react";
7279
+ import { NodeToolbar, Position as Position19, useViewport } from "@xyflow/react";
6814
7280
  import { ArrowRightLeft as ArrowRightLeft2, FileText } from "lucide-react";
6815
7281
  import { jsx as jsx35, jsxs as jsxs33 } from "react/jsx-runtime";
6816
7282
  var FocusModeNodeActions = ({
@@ -6843,7 +7309,7 @@ var FocusModeNodeActions = ({
6843
7309
  NodeToolbar,
6844
7310
  {
6845
7311
  nodeId: node.id,
6846
- position: Position18.Bottom,
7312
+ position: Position19.Bottom,
6847
7313
  isVisible: true,
6848
7314
  offset: -16,
6849
7315
  children: /* @__PURE__ */ jsx35(
@@ -6870,7 +7336,7 @@ var FocusModeNodeActions = ({
6870
7336
  NodeToolbar,
6871
7337
  {
6872
7338
  nodeId: node.id,
6873
- position: Position18.Bottom,
7339
+ position: Position19.Bottom,
6874
7340
  isVisible: true,
6875
7341
  offset: -16,
6876
7342
  children: /* @__PURE__ */ jsxs33(
@@ -6908,7 +7374,7 @@ var FocusModeNodeActions = ({
6908
7374
  var FocusModeNodeActions_default = FocusModeNodeActions;
6909
7375
 
6910
7376
  // src/components/FocusMode/FocusModePlaceholder.tsx
6911
- import { Handle as Handle21, Position as Position19 } from "@xyflow/react";
7377
+ import { Handle as Handle21, Position as Position20 } from "@xyflow/react";
6912
7378
  import { jsx as jsx36, jsxs as jsxs34 } from "react/jsx-runtime";
6913
7379
  var FocusModePlaceholder = ({
6914
7380
  data
@@ -6924,7 +7390,7 @@ var FocusModePlaceholder = ({
6924
7390
  Handle21,
6925
7391
  {
6926
7392
  type: "target",
6927
- position: Position19.Left,
7393
+ position: Position20.Left,
6928
7394
  style: { visibility: "hidden" }
6929
7395
  }
6930
7396
  ),
@@ -6933,7 +7399,7 @@ var FocusModePlaceholder = ({
6933
7399
  Handle21,
6934
7400
  {
6935
7401
  type: "source",
6936
- position: Position19.Right,
7402
+ position: Position20.Right,
6937
7403
  style: { visibility: "hidden" }
6938
7404
  }
6939
7405
  )
@@ -7860,7 +8326,7 @@ var MermaidView_default = MermaidView;
7860
8326
  import { useCallback as useCallback10, useMemo as useMemo17, useState as useState11, useEffect as useEffect7 } from "react";
7861
8327
 
7862
8328
  // src/utils/utils/utils.ts
7863
- import { MarkerType as MarkerType6, Position as Position20 } from "@xyflow/react";
8329
+ import { MarkerType as MarkerType6, Position as Position21 } from "@xyflow/react";
7864
8330
  import dagre from "dagre";
7865
8331
  var generateIdForNode = (node) => {
7866
8332
  return `${node.data.id}-${node.data.version}`;
@@ -7950,8 +8416,8 @@ var createEdge = (edgeOptions) => {
7950
8416
  };
7951
8417
  var createNode = (values) => {
7952
8418
  return {
7953
- sourcePosition: Position20.Right,
7954
- targetPosition: Position20.Left,
8419
+ sourcePosition: Position21.Right,
8420
+ targetPosition: Position21.Left,
7955
8421
  ...values
7956
8422
  };
7957
8423
  };
@@ -8083,7 +8549,7 @@ import {
8083
8549
  Maximize2 as Maximize23,
8084
8550
  Map as Map2,
8085
8551
  Sparkles,
8086
- Zap as Zap4,
8552
+ Zap as Zap5,
8087
8553
  EyeOff,
8088
8554
  ExternalLink,
8089
8555
  Save,
@@ -8208,7 +8674,7 @@ var VisualizerDropdownContent = memo33(
8208
8674
  onCheckedChange: toggleAnimateMessages,
8209
8675
  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",
8210
8676
  children: [
8211
- /* @__PURE__ */ jsx40(Zap4, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8677
+ /* @__PURE__ */ jsx40(Zap5, { className: "w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" }),
8212
8678
  /* @__PURE__ */ jsx40("span", { className: "flex-1 font-normal", children: "Simulate Messages" }),
8213
8679
  /* @__PURE__ */ jsx40(
8214
8680
  "div",
@@ -8992,6 +9458,120 @@ function flatLayout(nodes, edges, graphOpts, nodeSize, style) {
8992
9458
  return { nodes: layoutNodes, edges: layoutEdges };
8993
9459
  }
8994
9460
 
9461
+ // src/utils/local-packing.ts
9462
+ var toNumber = (value) => {
9463
+ if (typeof value === "number") return value;
9464
+ if (typeof value === "string") {
9465
+ const parsed = Number.parseFloat(value);
9466
+ return Number.isFinite(parsed) ? parsed : void 0;
9467
+ }
9468
+ return void 0;
9469
+ };
9470
+ var getPackableNodeSize = (node) => ({
9471
+ width: toNumber(node.measured?.width) ?? toNumber(node.width) ?? toNumber(node.style?.width) ?? 260,
9472
+ height: toNumber(node.measured?.height) ?? toNumber(node.height) ?? toNumber(node.style?.height) ?? 140
9473
+ });
9474
+ 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;
9475
+ var toRect = (node, y = node.position.y) => {
9476
+ const size = getPackableNodeSize(node);
9477
+ return {
9478
+ x: node.position.x,
9479
+ y,
9480
+ width: size.width,
9481
+ height: size.height
9482
+ };
9483
+ };
9484
+ var packNodesAroundBounds = ({
9485
+ nodes,
9486
+ movableNodeIds,
9487
+ protectedBounds,
9488
+ groupNodeId,
9489
+ gap = 40
9490
+ }) => {
9491
+ const movableNodes = nodes.filter((node) => movableNodeIds.has(node.id)).sort((a, b) => a.position.y - b.position.y);
9492
+ const movableIds = new Set(movableNodes.map((node) => node.id));
9493
+ const occupiedRects = [
9494
+ protectedBounds,
9495
+ ...nodes.filter(
9496
+ (node) => !node.parentId && node.id !== groupNodeId && !movableIds.has(node.id)
9497
+ ).map((node) => toRect(node))
9498
+ ];
9499
+ const plannedPositions = /* @__PURE__ */ new Map();
9500
+ const groupCenterY = protectedBounds.y + protectedBounds.height / 2;
9501
+ const placeNode = (node) => {
9502
+ const size = getPackableNodeSize(node);
9503
+ const nodeCenterY = node.position.y + size.height / 2;
9504
+ const moveDirection = nodeCenterY >= groupCenterY ? 1 : -1;
9505
+ let y = moveDirection > 0 ? Math.max(
9506
+ node.position.y,
9507
+ protectedBounds.y + protectedBounds.height + gap
9508
+ ) : Math.min(node.position.y, protectedBounds.y - size.height - gap);
9509
+ let attempts = 0;
9510
+ while (attempts < occupiedRects.length + 8) {
9511
+ const rect = {
9512
+ x: node.position.x,
9513
+ y,
9514
+ width: size.width,
9515
+ height: size.height
9516
+ };
9517
+ const collision = occupiedRects.find(
9518
+ (occupied) => rectsIntersect(rect, occupied, gap)
9519
+ );
9520
+ if (!collision) {
9521
+ occupiedRects.push(rect);
9522
+ plannedPositions.set(node.id, { ...node.position, y });
9523
+ return;
9524
+ }
9525
+ y = moveDirection > 0 ? collision.y + collision.height + gap : collision.y - size.height - gap;
9526
+ attempts += 1;
9527
+ }
9528
+ occupiedRects.push({
9529
+ x: node.position.x,
9530
+ y,
9531
+ width: size.width,
9532
+ height: size.height
9533
+ });
9534
+ plannedPositions.set(node.id, { ...node.position, y });
9535
+ };
9536
+ const below = movableNodes.filter((node) => {
9537
+ const size = getPackableNodeSize(node);
9538
+ return node.position.y + size.height / 2 >= groupCenterY;
9539
+ });
9540
+ const belowIds = new Set(below.map((node) => node.id));
9541
+ const above = movableNodes.filter((node) => !belowIds.has(node.id)).reverse();
9542
+ below.forEach(placeNode);
9543
+ above.forEach(placeNode);
9544
+ return plannedPositions;
9545
+ };
9546
+
9547
+ // src/utils/message-group-expansion.ts
9548
+ var getExpandedMessageGroupNode = (nodes, groupNodeId) => nodes.find(
9549
+ (node) => node.id === groupNodeId && node.type === "messageGroupExpanded"
9550
+ );
9551
+ var buildMessageGroupExpansionNodes = ({
9552
+ currentNodes,
9553
+ groupNodeId,
9554
+ expandedContainerNode,
9555
+ childNodes,
9556
+ downstreamNodes,
9557
+ getDownstreamPosition
9558
+ }) => {
9559
+ const withoutExistingGroup = currentNodes.filter(
9560
+ (node) => node.id !== groupNodeId && node.parentId !== groupNodeId
9561
+ );
9562
+ const existingIds = new Set(withoutExistingGroup.map((node) => node.id));
9563
+ const newDownstream = downstreamNodes.filter((node) => !existingIds.has(node.id)).map((node, index) => ({
9564
+ ...node,
9565
+ position: getDownstreamPosition(node, index)
9566
+ }));
9567
+ return [
9568
+ ...withoutExistingGroup,
9569
+ expandedContainerNode,
9570
+ ...childNodes,
9571
+ ...newDownstream
9572
+ ];
9573
+ };
9574
+
8995
9575
  // src/components/NotesToolbarButton.tsx
8996
9576
  import { useState as useState13, useCallback as useCallback11 } from "react";
8997
9577
  import {
@@ -9001,14 +9581,14 @@ import {
9001
9581
  Locate,
9002
9582
  ChevronRight,
9003
9583
  ServerIcon as ServerIcon2,
9004
- Zap as Zap5,
9005
- MessageSquare as MessageSquare2,
9584
+ Zap as Zap6,
9585
+ MessageSquare as MessageSquare3,
9006
9586
  Search as Search3,
9007
9587
  ArrowRightLeft as ArrowRightLeft3,
9008
- Database as Database5,
9588
+ Database as Database6,
9009
9589
  Package as Package2,
9010
9590
  Globe as Globe5,
9011
- User as User3,
9591
+ User as User4,
9012
9592
  MonitorIcon as MonitorIcon2,
9013
9593
  BoxesIcon as BoxesIcon2
9014
9594
  } from "lucide-react";
@@ -9018,18 +9598,18 @@ import { Fragment as Fragment11, jsx as jsx42, jsxs as jsxs40 } from "react/jsx-
9018
9598
  var NODE_TYPE_META = {
9019
9599
  service: { icon: ServerIcon2, color: "#ec4899", label: "Service" },
9020
9600
  services: { icon: ServerIcon2, color: "#ec4899", label: "Service" },
9021
- event: { icon: Zap5, color: "#f97316", label: "Event" },
9022
- events: { icon: Zap5, color: "#f97316", label: "Event" },
9023
- command: { icon: MessageSquare2, color: "#3b82f6", label: "Command" },
9024
- commands: { icon: MessageSquare2, color: "#3b82f6", label: "Command" },
9601
+ event: { icon: Zap6, color: "#f97316", label: "Event" },
9602
+ events: { icon: Zap6, color: "#f97316", label: "Event" },
9603
+ command: { icon: MessageSquare3, color: "#3b82f6", label: "Command" },
9604
+ commands: { icon: MessageSquare3, color: "#3b82f6", label: "Command" },
9025
9605
  query: { icon: Search3, color: "#22c55e", label: "Query" },
9026
9606
  queries: { icon: Search3, color: "#22c55e", label: "Query" },
9027
9607
  channel: { icon: ArrowRightLeft3, color: "#6b7280", label: "Channel" },
9028
9608
  channels: { icon: ArrowRightLeft3, color: "#6b7280", label: "Channel" },
9029
- data: { icon: Database5, color: "#3b82f6", label: "Data" },
9609
+ data: { icon: Database6, color: "#3b82f6", label: "Data" },
9030
9610
  "data-products": { icon: Package2, color: "#6366f1", label: "Data Product" },
9031
9611
  externalSystem: { icon: Globe5, color: "#ec4899", label: "External System" },
9032
- actor: { icon: User3, color: "#eab308", label: "Actor" },
9612
+ actor: { icon: User4, color: "#eab308", label: "Actor" },
9033
9613
  view: { icon: MonitorIcon2, color: "#8b5cf6", label: "View" },
9034
9614
  domain: { icon: BoxesIcon2, color: "#14b8a6", label: "Domain" },
9035
9615
  domains: { icon: BoxesIcon2, color: "#14b8a6", label: "Domain" }
@@ -9931,7 +10511,7 @@ var NodeGraphBuilder = ({
9931
10511
  );
9932
10512
  const [nodes, setNodes, onNodesChange] = useNodesState2(initialNodes);
9933
10513
  const [edges, setEdges, onEdgesChange] = useEdgesState2(initialEdges);
9934
- const { fitView, getNodes } = useReactFlow5();
10514
+ const { fitView, getNodes, getIntersectingNodes, getZoom, setCenter } = useReactFlow5();
9935
10515
  useEffect8(() => {
9936
10516
  setNodes(initialNodes);
9937
10517
  setEdges(initialEdges);
@@ -10071,57 +10651,107 @@ var NodeGraphBuilder = ({
10071
10651
  wrapper.classList.add("ec-animating-layout");
10072
10652
  setTimeout(() => wrapper.classList.remove("ec-animating-layout"), 400);
10073
10653
  }, []);
10074
- const relayoutGraph = useCallback12((nextNodes, nextEdges) => {
10075
- const g = new dagre3.graphlib.Graph({ compound: true });
10076
- g.setGraph({ rankdir: "LR", ranksep: 300, nodesep: 50 });
10077
- g.setDefaultEdgeLabel(() => ({}));
10078
- nextNodes.forEach((node) => {
10079
- if (node.parentId) return;
10080
- const w = node.style?.width || (node.type === "messageGroupExpanded" ? 380 : 150);
10081
- const h = node.style?.height || (node.type === "messageGroupExpanded" ? 0 : 120);
10082
- if (node.type === "messageGroupExpanded") {
10083
- const children = nextNodes.filter((n) => n.parentId === node.id);
10084
- const childHeight = children.length * 190 + 100;
10085
- g.setNode(node.id, { width: w, height: childHeight });
10086
- } else {
10087
- g.setNode(node.id, { width: w, height: h });
10088
- }
10089
- });
10090
- nextEdges.forEach((edge) => {
10091
- const sourceNode = nextNodes.find((n) => n.id === edge.source);
10092
- const targetNode = nextNodes.find((n) => n.id === edge.target);
10093
- const sourceTop = sourceNode?.parentId || edge.source;
10094
- const targetTop = targetNode?.parentId || edge.target;
10095
- if (g.hasNode(sourceTop) && g.hasNode(targetTop) && sourceTop !== targetTop) {
10096
- g.setEdge(sourceTop, targetTop);
10097
- }
10098
- });
10099
- dagre3.layout(g);
10100
- const positioned = nextNodes.map((node) => {
10101
- if (node.parentId) {
10102
- const parent = nextNodes.find((n) => n.id === node.parentId);
10103
- if (parent?.type === "flowExpanded") {
10104
- return node;
10654
+ const relayoutGraph = useCallback12(
10655
+ (nextNodes, nextEdges, anchor) => {
10656
+ const g = new dagre3.graphlib.Graph({ compound: true });
10657
+ g.setGraph({ rankdir: "LR", ranksep: 300, nodesep: 50 });
10658
+ g.setDefaultEdgeLabel(() => ({}));
10659
+ nextNodes.forEach((node) => {
10660
+ if (node.parentId) return;
10661
+ const w = node.style?.width || (node.type === "messageGroupExpanded" ? 380 : 150);
10662
+ const h = node.style?.height || (node.type === "messageGroupExpanded" ? 0 : 120);
10663
+ if (node.type === "messageGroupExpanded") {
10664
+ const children = nextNodes.filter((n) => n.parentId === node.id);
10665
+ const childHeight = children.length * 190 + 100;
10666
+ g.setNode(node.id, { width: w, height: childHeight });
10667
+ } else {
10668
+ g.setNode(node.id, { width: w, height: h });
10669
+ }
10670
+ });
10671
+ nextEdges.forEach((edge) => {
10672
+ const sourceNode = nextNodes.find((n) => n.id === edge.source);
10673
+ const targetNode = nextNodes.find((n) => n.id === edge.target);
10674
+ const sourceTop = sourceNode?.parentId || edge.source;
10675
+ const targetTop = targetNode?.parentId || edge.target;
10676
+ if (g.hasNode(sourceTop) && g.hasNode(targetTop) && sourceTop !== targetTop) {
10677
+ g.setEdge(sourceTop, targetTop);
10105
10678
  }
10106
- const parentWidth = parent?.style?.width || 380;
10107
- const childWidth = 240;
10108
- const xOffset = Math.max(20, (parentWidth - childWidth) / 2);
10109
- const siblings = nextNodes.filter((n) => n.parentId === node.parentId);
10110
- const index = siblings.indexOf(node);
10679
+ });
10680
+ dagre3.layout(g);
10681
+ const positioned = nextNodes.map((node) => {
10682
+ if (node.parentId) {
10683
+ const parent = nextNodes.find((n) => n.id === node.parentId);
10684
+ if (parent?.type === "flowExpanded") {
10685
+ return node;
10686
+ }
10687
+ const parentWidth = parent?.style?.width || 380;
10688
+ const childWidth = 240;
10689
+ const xOffset = Math.max(20, (parentWidth - childWidth) / 2);
10690
+ const siblings = nextNodes.filter(
10691
+ (n) => n.parentId === node.parentId
10692
+ );
10693
+ const index = siblings.indexOf(node);
10694
+ return {
10695
+ ...node,
10696
+ position: { x: xOffset, y: 70 + index * 190 }
10697
+ };
10698
+ }
10699
+ const pos = g.node(node.id);
10700
+ if (!pos) return node;
10111
10701
  return {
10112
10702
  ...node,
10113
- position: { x: xOffset, y: 70 + index * 190 }
10703
+ position: { x: pos.x - pos.width / 2, y: pos.y - pos.height / 2 }
10114
10704
  };
10115
- }
10116
- const pos = g.node(node.id);
10117
- if (!pos) return node;
10118
- return {
10119
- ...node,
10120
- position: { x: pos.x - pos.width / 2, y: pos.y - pos.height / 2 }
10705
+ });
10706
+ if (!anchor) return positioned;
10707
+ const positionedAnchor = positioned.find((node) => node.id === anchor.id);
10708
+ if (!positionedAnchor) return positioned;
10709
+ const offset = {
10710
+ x: anchor.position.x - positionedAnchor.position.x,
10711
+ y: anchor.position.y - positionedAnchor.position.y
10121
10712
  };
10122
- });
10123
- return positioned;
10124
- }, []);
10713
+ return positioned.map((node) => {
10714
+ if (node.parentId) return node;
10715
+ return {
10716
+ ...node,
10717
+ position: {
10718
+ x: node.position.x + offset.x,
10719
+ y: node.position.y + offset.y
10720
+ }
10721
+ };
10722
+ });
10723
+ },
10724
+ []
10725
+ );
10726
+ const makeRoomForRenderedExpandedGroup = useCallback12(
10727
+ (groupNodeId, groupBounds) => {
10728
+ const padding = 80;
10729
+ const protectedBounds = {
10730
+ x: groupBounds.x - padding,
10731
+ y: groupBounds.y - padding,
10732
+ width: groupBounds.width + padding * 2,
10733
+ height: groupBounds.height + padding * 2
10734
+ };
10735
+ const intersectingIds = new Set(
10736
+ getIntersectingNodes(protectedBounds, true).filter((node) => node.id !== groupNodeId && !node.parentId).map((node) => node.id)
10737
+ );
10738
+ if (intersectingIds.size === 0) return;
10739
+ setNodes((currentNodes) => {
10740
+ const plannedPositions = packNodesAroundBounds({
10741
+ nodes: currentNodes,
10742
+ movableNodeIds: intersectingIds,
10743
+ protectedBounds,
10744
+ groupNodeId
10745
+ });
10746
+ return currentNodes.map((node) => {
10747
+ const plannedPosition = plannedPositions.get(node.id);
10748
+ if (!plannedPosition) return node;
10749
+ return { ...node, position: plannedPosition };
10750
+ });
10751
+ });
10752
+ },
10753
+ [getIntersectingNodes, setNodes]
10754
+ );
10125
10755
  const layoutSubFlowChildren = useCallback12(
10126
10756
  (children, edges2, sizeOf, opts) => {
10127
10757
  const { padding, headerH, fallbackW = 240, fallbackH = 120 } = opts;
@@ -10219,9 +10849,19 @@ var NodeGraphBuilder = ({
10219
10849
  }
10220
10850
  const without = prev.filter(
10221
10851
  (n) => n.id !== groupNodeId && !childNodeIds.has(n.id) && !(downstreamNodeIds.has(n.id) && !referencedByEdges.has(n.id))
10222
- );
10223
- const next = [...without, originalNode];
10224
- return relayoutGraph(next, nextEdges);
10852
+ ).map((n) => {
10853
+ const stashedPosition = stashed?.nodePositions?.[n.id];
10854
+ if (!stashedPosition || n.parentId) return n;
10855
+ return { ...n, position: stashedPosition };
10856
+ });
10857
+ const next = [
10858
+ ...without,
10859
+ {
10860
+ ...originalNode,
10861
+ position: stashed?.nodePositions?.[groupNodeId] ?? expandedNode.position
10862
+ }
10863
+ ];
10864
+ return next;
10225
10865
  });
10226
10866
  setEdges((prev) => {
10227
10867
  const without = prev.filter(
@@ -10229,9 +10869,6 @@ var NodeGraphBuilder = ({
10229
10869
  );
10230
10870
  return [...without, ...originalEdges];
10231
10871
  });
10232
- requestAnimationFrame(() => {
10233
- fitView({ duration: 400, padding: 0.2 });
10234
- });
10235
10872
  },
10236
10873
  [
10237
10874
  initialNodes,
@@ -10239,8 +10876,7 @@ var NodeGraphBuilder = ({
10239
10876
  setNodes,
10240
10877
  setEdges,
10241
10878
  relayoutGraph,
10242
- animateLayout,
10243
- fitView
10879
+ animateLayout
10244
10880
  ]
10245
10881
  );
10246
10882
  useEffect8(() => {
@@ -10323,9 +10959,6 @@ var NodeGraphBuilder = ({
10323
10959
  onNodeClick(node);
10324
10960
  return;
10325
10961
  }
10326
- if (linksToVisualiser && onNavigate) {
10327
- return;
10328
- }
10329
10962
  const isFlow = edgesRef.current.some(
10330
10963
  (edge) => edge.type === "flow-edge"
10331
10964
  );
@@ -10339,6 +10972,24 @@ var NodeGraphBuilder = ({
10339
10972
  if (node.type === "messageGroup") {
10340
10973
  const groupData = node.data;
10341
10974
  const groupNodeId = node.id;
10975
+ const currentGroupNode = getExpandedMessageGroupNode(
10976
+ nodesRef.current,
10977
+ groupNodeId
10978
+ );
10979
+ if (currentGroupNode?.type === "messageGroupExpanded") {
10980
+ const measured = currentGroupNode?.measured;
10981
+ const width = measured?.width ?? currentGroupNode.style?.width ?? 380;
10982
+ const height = measured?.height ?? currentGroupNode.style?.height ?? 300;
10983
+ setCenter(
10984
+ currentGroupNode.position.x + width / 2,
10985
+ currentGroupNode.position.y + height / 2,
10986
+ {
10987
+ duration: 300,
10988
+ zoom: Math.min(Math.max(getZoom(), 0.55), 1)
10989
+ }
10990
+ );
10991
+ return;
10992
+ }
10342
10993
  const serviceNodeId = `${groupData.service.id}-${groupData.service.version}`;
10343
10994
  const childCount = groupData.messages?.length || 0;
10344
10995
  const containerWidth = 380;
@@ -10347,6 +10998,9 @@ var NodeGraphBuilder = ({
10347
10998
  (e) => e.source === groupNodeId || e.target === groupNodeId
10348
10999
  );
10349
11000
  const preExpansionNodeIds = nodesRef.current.map((n) => n.id);
11001
+ const preExpansionNodePositions = Object.fromEntries(
11002
+ nodesRef.current.map((n) => [n.id, { ...n.position }])
11003
+ );
10350
11004
  const expandedContainerNode = {
10351
11005
  id: groupNodeId,
10352
11006
  type: "messageGroupExpanded",
@@ -10359,7 +11013,8 @@ var NodeGraphBuilder = ({
10359
11013
  __preExpansion: {
10360
11014
  node,
10361
11015
  edges: preExpansionEdges,
10362
- nodeIds: preExpansionNodeIds
11016
+ nodeIds: preExpansionNodeIds,
11017
+ nodePositions: preExpansionNodePositions
10363
11018
  }
10364
11019
  },
10365
11020
  style: {
@@ -10420,24 +11075,19 @@ var NodeGraphBuilder = ({
10420
11075
  });
10421
11076
  animateLayout();
10422
11077
  setNodes((prev) => {
10423
- const without = prev.filter((n) => n.id !== groupNodeId);
10424
- const existingIds = new Set(without.map((n) => n.id));
10425
- const newDownstream = downstreamNodes.filter(
10426
- (n) => !existingIds.has(n.id)
10427
- );
10428
- const next = [
10429
- ...without,
11078
+ const downstreamX = groupData.direction === "sends" ? node.position.x + containerWidth + 260 : node.position.x - 420;
11079
+ const downstreamY = node.position.y + 40;
11080
+ return buildMessageGroupExpansionNodes({
11081
+ currentNodes: prev,
11082
+ groupNodeId,
10430
11083
  expandedContainerNode,
10431
- ...childNodes,
10432
- ...newDownstream
10433
- ];
10434
- return relayoutGraph(next, [
10435
- ...edgesRef.current.filter(
10436
- (e) => e.source !== groupNodeId && e.target !== groupNodeId
10437
- ),
10438
- ...childEdges,
10439
- ...downstreamEdges
10440
- ]);
11084
+ childNodes,
11085
+ downstreamNodes,
11086
+ getDownstreamPosition: (_downstreamNode, index) => ({
11087
+ x: downstreamX,
11088
+ y: downstreamY + index * 190
11089
+ })
11090
+ });
10441
11091
  });
10442
11092
  setEdges((prev) => {
10443
11093
  const without = prev.filter(
@@ -10446,6 +11096,12 @@ var NodeGraphBuilder = ({
10446
11096
  return [...without, ...childEdges, ...downstreamEdges];
10447
11097
  });
10448
11098
  requestAnimationFrame(() => {
11099
+ let actualContainerBounds = {
11100
+ x: node.position.x,
11101
+ y: node.position.y,
11102
+ width: containerWidth,
11103
+ height: containerHeight
11104
+ };
10449
11105
  setNodes((prev) => {
10450
11106
  const children = prev.filter((n) => n.parentId === groupNodeId);
10451
11107
  if (children.length === 0) return prev;
@@ -10465,8 +11121,20 @@ var NodeGraphBuilder = ({
10465
11121
  const totalChildH = measurements.reduce((sum, m) => sum + m.h, 0) + gap * (measurements.length - 1);
10466
11122
  const actualContainerH = headerH + totalChildH + padding * 2;
10467
11123
  let currentY = headerH + padding;
11124
+ actualContainerBounds = {
11125
+ x: node.position.x,
11126
+ y: node.position.y,
11127
+ width: containerWidth,
11128
+ height: actualContainerH
11129
+ };
10468
11130
  return prev.map((n) => {
10469
11131
  if (n.id === groupNodeId) {
11132
+ actualContainerBounds = {
11133
+ x: n.position.x,
11134
+ y: n.position.y,
11135
+ width: containerWidth,
11136
+ height: actualContainerH
11137
+ };
10470
11138
  return {
10471
11139
  ...n,
10472
11140
  style: {
@@ -10484,9 +11152,23 @@ var NodeGraphBuilder = ({
10484
11152
  return { ...n, position: { x, y } };
10485
11153
  });
10486
11154
  });
10487
- });
10488
- requestAnimationFrame(() => {
10489
- fitView({ duration: 400, padding: 0.2 });
11155
+ requestAnimationFrame(() => {
11156
+ const groupNode = getNodes().find((n) => n.id === groupNodeId);
11157
+ const measured = groupNode?.measured;
11158
+ const width = measured?.width ?? groupNode?.style?.width ?? containerWidth;
11159
+ const height = measured?.height ?? groupNode?.style?.height ?? actualContainerBounds.height;
11160
+ const bounds = {
11161
+ x: groupNode?.position.x ?? actualContainerBounds.x,
11162
+ y: groupNode?.position.y ?? actualContainerBounds.y,
11163
+ width,
11164
+ height
11165
+ };
11166
+ makeRoomForRenderedExpandedGroup(groupNodeId, bounds);
11167
+ setCenter(bounds.x + width / 2, bounds.y + height / 2, {
11168
+ duration: 450,
11169
+ zoom: Math.min(Math.max(getZoom(), 0.55), 1)
11170
+ });
11171
+ });
10490
11172
  });
10491
11173
  return;
10492
11174
  }
@@ -10651,10 +11333,21 @@ var NodeGraphBuilder = ({
10651
11333
  });
10652
11334
  return;
10653
11335
  }
11336
+ if (linksToVisualiser && onNavigate) {
11337
+ return;
11338
+ }
10654
11339
  setFocusedNodeId(node.id);
10655
11340
  setFocusModeOpen(true);
10656
11341
  },
10657
- [onNodeClick, linksToVisualiser, onNavigate]
11342
+ [
11343
+ onNodeClick,
11344
+ linksToVisualiser,
11345
+ onNavigate,
11346
+ makeRoomForRenderedExpandedGroup,
11347
+ getNodes,
11348
+ getZoom,
11349
+ setCenter
11350
+ ]
10658
11351
  );
10659
11352
  const toggleAnimateMessages = useCallback12(() => {
10660
11353
  setAnimateMessages((prev) => {
@@ -10884,13 +11577,15 @@ var NodeGraphBuilder = ({
10884
11577
  }, [getNodes, downloadImage, title]);
10885
11578
  const handleLegendClick = useCallback12(
10886
11579
  (collectionType, groupId) => {
11580
+ const isLegendTarget = (node) => {
11581
+ if (groupId) {
11582
+ return node.data.group && node.data.group?.id === groupId;
11583
+ }
11584
+ return node.type === collectionType;
11585
+ };
10887
11586
  const updatedNodes = nodes.map((node) => {
10888
- if (groupId && node.data.group && node.data.group?.id === groupId) {
11587
+ if (isLegendTarget(node)) {
10889
11588
  return { ...node, style: { ...node.style, opacity: 1 } };
10890
- } else {
10891
- if (node.type === collectionType) {
10892
- return { ...node, style: { ...node.style, opacity: 1 } };
10893
- }
10894
11589
  }
10895
11590
  return { ...node, style: { ...node.style, opacity: 0.1 } };
10896
11591
  });
@@ -10905,10 +11600,12 @@ var NodeGraphBuilder = ({
10905
11600
  });
10906
11601
  setNodes(updatedNodes);
10907
11602
  setEdges(updatedEdges);
11603
+ const targetNodes = updatedNodes.filter(isLegendTarget);
11604
+ if (targetNodes.length === 0) return;
10908
11605
  fitView({
10909
11606
  padding: 0.2,
10910
11607
  duration: 800,
10911
- nodes: updatedNodes.filter((node) => node.type === collectionType)
11608
+ nodes: targetNodes
10912
11609
  });
10913
11610
  },
10914
11611
  [nodes, edges, setNodes, setEdges, fitView]