@oml/markdown 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -62149,11 +62149,19 @@
62149
62149
  container.appendChild(chartRoot);
62150
62150
  const canvasEl = document.createElement("canvas");
62151
62151
  chartRoot.appendChild(canvasEl);
62152
- initializeChartWhenReady(chartRoot, canvasEl, payload.columns, payload.rows, parsed, container);
62152
+ initializeChartWhenReady(
62153
+ chartRoot,
62154
+ canvasEl,
62155
+ payload.columns,
62156
+ payload.rows,
62157
+ parsed,
62158
+ container,
62159
+ (content9) => this.requestTextFileDownload(content9, "chart", "csv")
62160
+ );
62153
62161
  return container;
62154
62162
  }
62155
62163
  };
62156
- function initializeChartWhenReady(chartRoot, canvasEl, columns, rows, parsed, messageContainer) {
62164
+ function initializeChartWhenReady(chartRoot, canvasEl, columns, rows, parsed, messageContainer, downloadCsv) {
62157
62165
  const maxAttempts = 20;
62158
62166
  let attempts = 0;
62159
62167
  const tryInit = () => {
@@ -62162,7 +62170,7 @@
62162
62170
  if (attempts < maxAttempts) requestAnimationFrame(tryInit);
62163
62171
  return;
62164
62172
  }
62165
- void doChartInit(chartRoot, canvasEl, columns, rows, parsed).catch((err) => {
62173
+ void doChartInit(chartRoot, canvasEl, columns, rows, parsed, downloadCsv).catch((err) => {
62166
62174
  const detail = err instanceof Error ? err.message : String(err);
62167
62175
  const msg = document.createElement("div");
62168
62176
  msg.className = "oml-md-result-message";
@@ -62172,7 +62180,7 @@
62172
62180
  };
62173
62181
  requestAnimationFrame(tryInit);
62174
62182
  }
62175
- async function doChartInit(chartRoot, canvasEl, columns, rows, parsed) {
62183
+ async function doChartInit(chartRoot, canvasEl, columns, rows, parsed, downloadCsv) {
62176
62184
  const ChartCtor = await loadChartJs();
62177
62185
  let chart = null;
62178
62186
  const buildAndRender = () => {
@@ -62184,6 +62192,7 @@
62184
62192
  chart = new ChartCtor(canvasEl, config);
62185
62193
  };
62186
62194
  buildAndRender();
62195
+ installChartToolbar(chartRoot, columns, rows, downloadCsv);
62187
62196
  const resizeObserver = new ResizeObserver(() => {
62188
62197
  if (!chartRoot.isConnected) return;
62189
62198
  chart?.resize();
@@ -62256,6 +62265,73 @@
62256
62265
  resizeHandle.addEventListener("pointerup", onResizePointerEnd);
62257
62266
  resizeHandle.addEventListener("pointercancel", onResizePointerEnd);
62258
62267
  }
62268
+ function installChartToolbar(chartRoot, columns, rows, downloadCsv) {
62269
+ if (chartRoot.querySelector(".graph-corner-toolbar")) {
62270
+ return;
62271
+ }
62272
+ const hotspot = document.createElement("div");
62273
+ hotspot.className = "graph-corner-hotspot";
62274
+ chartRoot.appendChild(hotspot);
62275
+ const toolbar = document.createElement("div");
62276
+ toolbar.className = "graph-corner-toolbar";
62277
+ const downloadButton = createDownloadButton();
62278
+ downloadButton.addEventListener("click", () => {
62279
+ downloadCsv(toCsv(columns, rows));
62280
+ });
62281
+ toolbar.appendChild(downloadButton);
62282
+ chartRoot.appendChild(toolbar);
62283
+ let hideTimer = 0;
62284
+ const showToolbar = () => {
62285
+ if (hideTimer) {
62286
+ window.clearTimeout(hideTimer);
62287
+ hideTimer = 0;
62288
+ }
62289
+ chartRoot.classList.add("graph-toolbar-visible");
62290
+ };
62291
+ const scheduleHideToolbar = () => {
62292
+ hideTimer = window.setTimeout(() => {
62293
+ chartRoot.classList.remove("graph-toolbar-visible");
62294
+ hideTimer = 0;
62295
+ }, 120);
62296
+ };
62297
+ hotspot.addEventListener("mouseenter", showToolbar);
62298
+ toolbar.addEventListener("mouseenter", showToolbar);
62299
+ hotspot.addEventListener("mouseleave", scheduleHideToolbar);
62300
+ toolbar.addEventListener("mouseleave", scheduleHideToolbar);
62301
+ const swallowPointer = (event) => {
62302
+ event.stopPropagation();
62303
+ };
62304
+ toolbar.addEventListener("pointerdown", swallowPointer, true);
62305
+ toolbar.addEventListener("mousedown", swallowPointer, true);
62306
+ downloadButton.addEventListener("pointerdown", swallowPointer, true);
62307
+ downloadButton.addEventListener("mousedown", swallowPointer, true);
62308
+ }
62309
+ function createDownloadButton() {
62310
+ const downloadButton = document.createElement("button");
62311
+ downloadButton.className = "tree-download-btn";
62312
+ downloadButton.title = "Download CSV";
62313
+ downloadButton.setAttribute("aria-label", "Download CSV");
62314
+ const iconSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
62315
+ iconSvg.setAttribute("viewBox", "0 0 24 24");
62316
+ iconSvg.setAttribute("width", "20");
62317
+ iconSvg.setAttribute("height", "20");
62318
+ iconSvg.setAttribute("aria-hidden", "true");
62319
+ iconSvg.setAttribute("focusable", "false");
62320
+ iconSvg.style.fill = "currentColor";
62321
+ const iconPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
62322
+ iconPath.setAttribute("d", "M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z");
62323
+ iconSvg.appendChild(iconPath);
62324
+ downloadButton.appendChild(iconSvg);
62325
+ return downloadButton;
62326
+ }
62327
+ function toCsv(columns, rows) {
62328
+ const escape = (value) => `"${String(value ?? "").replace(/"/g, '""')}"`;
62329
+ const lines = [columns.map(escape).join(",")];
62330
+ for (const row of rows) {
62331
+ lines.push(columns.map((_2, index3) => escape(row[index3] ?? "")).join(","));
62332
+ }
62333
+ return lines.join("\n");
62334
+ }
62259
62335
  async function loadChartJs() {
62260
62336
  if (chartClass) return chartClass;
62261
62337
  const mod3 = await Promise.resolve().then(() => (init_auto(), auto_exports));
@@ -62520,12 +62596,15 @@
62520
62596
  const tripleIndex = indexTriples(rows);
62521
62597
  const stylesheet = parseDiagramStylesheet(result.options);
62522
62598
  const compiled = compileDiagramGraph(tripleIndex, stylesheet);
62523
- const layoutOptions = resolveDagreLayoutOptions(result.options);
62599
+ const diagramStyle = resolveElementStyle("diagram", "diagram", [], {}, stylesheet);
62600
+ const layoutOptions = resolveDagreLayoutOptions(diagramStyle);
62601
+ const rootSpacing = resolveRootSpacing(diagramStyle);
62524
62602
  if (compiled.nodes.length === 0) {
62525
62603
  container.appendChild(this.createMessageContainer("No diagram nodes were inferred from the diagram namespace triples."));
62526
62604
  return container;
62527
62605
  }
62528
62606
  void renderWithX6(canvas, baseId, compiled, layoutOptions, {
62607
+ rootSpacing,
62529
62608
  downloadSvg: (content9) => this.requestTextFileDownload(content9, "diagram", "svg")
62530
62609
  }).catch((error) => {
62531
62610
  const detail = error instanceof Error ? error.message : String(error);
@@ -62541,7 +62620,7 @@
62541
62620
  return;
62542
62621
  }
62543
62622
  const GraphCtor = await loadX6GraphCtor();
62544
- const layout = await layoutGraphDagre(graph, layoutOptions);
62623
+ const layout = await layoutGraphDagre(graph, layoutOptions, actions.rootSpacing);
62545
62624
  const minHeight = asFiniteNumber(
62546
62625
  parseCssPixels(liveCanvas.style.getPropertyValue("--oml-diagram-min-height")),
62547
62626
  numericCanvasMinHeight(void 0)
@@ -62552,23 +62631,19 @@
62552
62631
  let isResizingCanvas = false;
62553
62632
  const isPortPointerTarget = (event) => {
62554
62633
  const target = event?.target;
62555
- if (target?.closest(".x6-port, .x6-port-body, .oml-port-body")) {
62634
+ if (target?.closest(".x6-port, .x6-port-body, .oml-port-body, [port]")) {
62556
62635
  return true;
62557
62636
  }
62558
62637
  const path2 = typeof event?.composedPath === "function" ? event.composedPath() : [];
62559
- return path2.some((entry) => entry instanceof Element && (entry.classList.contains("x6-port") || entry.classList.contains("x6-port-body") || entry.classList.contains("oml-port-body")));
62560
- };
62561
- const findPortIdFromTarget = (target) => {
62562
- if (!(target instanceof Element)) return void 0;
62563
- const portHost = target.closest(".x6-port");
62564
- if (!portHost) return void 0;
62565
- return portHost.getAttribute("port") ?? portHost.getAttribute("data-port-id") ?? void 0;
62638
+ return path2.some((entry) => entry instanceof Element && (entry.classList.contains("x6-port") || entry.classList.contains("x6-port-body") || entry.classList.contains("oml-port-body") || entry.hasAttribute("port")));
62566
62639
  };
62567
62640
  const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
62568
62641
  const isCompartmentNode = (node) => {
62569
62642
  const nodeId = String(node?.id ?? "");
62570
62643
  return nodeById.get(nodeId)?.kind === "Compartment";
62571
62644
  };
62645
+ const boundPortContainers = /* @__PURE__ */ new WeakSet();
62646
+ let nodeTransform;
62572
62647
  const graphView = new GraphCtor({
62573
62648
  container: liveCanvas,
62574
62649
  autoResize: false,
@@ -62582,7 +62657,10 @@
62582
62657
  },
62583
62658
  connecting: {
62584
62659
  router: "normal",
62585
- connector: "rounded",
62660
+ connector: {
62661
+ name: "jumpover",
62662
+ args: { size: 5 }
62663
+ },
62586
62664
  allowBlank: false,
62587
62665
  allowNode: false,
62588
62666
  allowPort: false,
@@ -62598,13 +62676,63 @@
62598
62676
  edgeMovable: false,
62599
62677
  vertexMovable: false,
62600
62678
  arrowheadMovable: false,
62601
- labelMovable: false
62679
+ labelMovable: true
62602
62680
  };
62603
62681
  },
62604
62682
  background: {
62605
62683
  color: CSS_CANVAS_BACKGROUND
62684
+ },
62685
+ onPortRendered: ({ port: port2, node, container }) => {
62686
+ if (boundPortContainers.has(container)) {
62687
+ return;
62688
+ }
62689
+ boundPortContainers.add(container);
62690
+ container.setAttribute("data-port-id", String(port2.id));
62691
+ container.style.cursor = "grab";
62692
+ container.style.touchAction = "none";
62693
+ container.addEventListener("pointerdown", (event) => {
62694
+ if (typeof graphView.cleanSelection === "function") {
62695
+ graphView.cleanSelection();
62696
+ }
62697
+ nodeTransform.clearWidgets();
62698
+ const owner = graphView.getCellById(String(node.id));
62699
+ if (!owner) {
62700
+ return;
62701
+ }
62702
+ startPortDrag(event.clientX, event.clientY, owner, String(port2.id), event.pointerId, container);
62703
+ event.preventDefault();
62704
+ event.stopPropagation();
62705
+ if (typeof event.stopImmediatePropagation === "function") {
62706
+ event.stopImmediatePropagation();
62707
+ }
62708
+ if (typeof container.setPointerCapture === "function") {
62709
+ try {
62710
+ container.setPointerCapture(event.pointerId);
62711
+ } catch {
62712
+ }
62713
+ }
62714
+ });
62606
62715
  }
62607
62716
  });
62717
+ const x6Mod = await Promise.resolve().then(() => (init_es(), es_exports));
62718
+ const TransformCtor = x6Mod.Transform;
62719
+ if (typeof TransformCtor !== "function") {
62720
+ throw new Error("X6 Transform plugin is unavailable in @antv/x6");
62721
+ }
62722
+ nodeTransform = new TransformCtor({
62723
+ resizing: {
62724
+ enabled: (node) => node?.getData?.()?.kind === "Node",
62725
+ minWidth: 48,
62726
+ minHeight: 32,
62727
+ orthogonal: true,
62728
+ restrict: false,
62729
+ autoScroll: false,
62730
+ preserveAspectRatio: false,
62731
+ allowReverse: false
62732
+ },
62733
+ rotating: false
62734
+ });
62735
+ graphView.use(nodeTransform);
62608
62736
  const toPlainRect = (value) => {
62609
62737
  if (!value) return void 0;
62610
62738
  const x = Number(value.x);
@@ -62637,12 +62765,7 @@
62637
62765
  for (const list of portsByOwner.values()) {
62638
62766
  list.sort((a, b) => a.id.localeCompare(b.id));
62639
62767
  }
62640
- const ownerByPortId = /* @__PURE__ */ new Map();
62641
- for (const [ownerId, ports] of portsByOwner.entries()) {
62642
- for (const port2 of ports) {
62643
- ownerByPortId.set(port2.id, ownerId);
62644
- }
62645
- }
62768
+ const portPlacementById = computeDefaultPortPlacements(graph, nodeById, portsByOwner, layout.boxes);
62646
62769
  const ordered = [...graph.nodes].filter((node) => node.kind !== "Port").sort((a, b) => nodeDepth(a.id, nodeById) - nodeDepth(b.id, nodeById));
62647
62770
  for (const node of ordered) {
62648
62771
  const box = layout.boxes.get(node.id);
@@ -62650,19 +62773,39 @@
62650
62773
  const labelText = node.labels.length > 0 ? node.labels.join("\n") : localName(node.id);
62651
62774
  const resolvedStyle = node.style;
62652
62775
  const resolvedShape = resolveRenderNodeShape(resolvedStyle);
62776
+ const { bodyAttrs, labelAttrs, iconSvgAttrs, iconPathAttrs, imageAttrs } = resolveNodeAttrs(resolvedStyle);
62653
62777
  const x = box.x;
62654
62778
  const y2 = box.y;
62655
62779
  const ownerPorts = portsByOwner.get(node.id) ?? [];
62656
- const portItems = ownerPorts.map((port2, index3) => ({
62657
- id: port2.id,
62658
- group: "boundary",
62659
- args: {
62660
- x: box.width,
62661
- y: (index3 + 1) * box.height / (ownerPorts.length + 1)
62662
- },
62663
- attrs: resolvePortAttrs(port2.style, port2.classes, port2.labels[0] ?? "Port")
62664
- }));
62665
- const { bodyAttrs, labelAttrs, iconSvgAttrs, iconPathAttrs, imageAttrs } = resolveNodeAttrs(resolvedStyle);
62780
+ const portItems = ownerPorts.map((port2) => {
62781
+ const placement = portPlacementById.get(port2.id) ?? { side: "right", ratio: 0.5 };
62782
+ const ratio2 = clamp3(placement.ratio, 0.05, 0.95);
62783
+ const position2 = placement.side === "left" || placement.side === "right" ? {
62784
+ x: placement.side === "left" ? 0 : box.width,
62785
+ y: ratio2 * box.height
62786
+ } : {
62787
+ x: ratio2 * box.width,
62788
+ y: placement.side === "top" ? 0 : box.height
62789
+ };
62790
+ const portText = port2.labels[0];
62791
+ return {
62792
+ id: port2.id,
62793
+ group: "boundary",
62794
+ args: {
62795
+ x: position2.x,
62796
+ y: position2.y,
62797
+ side: placement.side,
62798
+ ratio: ratio2
62799
+ },
62800
+ attrs: resolvePortAttrs(
62801
+ port2.style,
62802
+ port2.classes,
62803
+ portText,
62804
+ placement.side,
62805
+ typeof bodyAttrs.stroke === "string" ? bodyAttrs.stroke : void 0
62806
+ )
62807
+ };
62808
+ });
62666
62809
  const iconPathSelectors = iconPathAttrs.map((_2, index3) => `iconPath${index3}`);
62667
62810
  graphView.addNode({
62668
62811
  id: node.id,
@@ -62766,7 +62909,7 @@
62766
62909
  },
62767
62910
  items: portItems
62768
62911
  } : void 0,
62769
- zIndex: 10,
62912
+ zIndex: 50,
62770
62913
  data: {
62771
62914
  kind: node.kind,
62772
62915
  ownerId: node.parentId
@@ -62893,10 +63036,30 @@
62893
63036
  let maxBottom = Number.NEGATIVE_INFINITY;
62894
63037
  const childCells = [];
62895
63038
  const childDebug = [];
63039
+ const childGeometryBounds = (child) => {
63040
+ if (!child || typeof child.size !== "function") {
63041
+ return void 0;
63042
+ }
63043
+ const size = child.size();
63044
+ const width2 = Number(size?.width);
63045
+ const height2 = Number(size?.height);
63046
+ let position2;
63047
+ if (typeof child.getPosition === "function") {
63048
+ position2 = child.getPosition();
63049
+ } else {
63050
+ position2 = child.position;
63051
+ }
63052
+ const x = Number(position2?.x);
63053
+ const y2 = Number(position2?.y);
63054
+ if (![x, y2, width2, height2].every(Number.isFinite)) {
63055
+ return void 0;
63056
+ }
63057
+ return { x, y: y2, width: width2, height: height2 };
63058
+ };
62896
63059
  for (const childId of childIds) {
62897
63060
  const child = graphView.getCellById(childId);
62898
- if (!child || typeof child.getBBox !== "function") continue;
62899
- const absBBox = child.getBBox();
63061
+ const absBBox = childGeometryBounds(child);
63062
+ if (!absBBox) continue;
62900
63063
  const relLeft = absBBox.x - containerBBox.x;
62901
63064
  const relTop = absBBox.y - containerBBox.y;
62902
63065
  minLeft = Math.min(minLeft, relLeft);
@@ -62924,9 +63087,11 @@
62924
63087
  });
62925
63088
  return;
62926
63089
  }
62927
- const topPadding = containerSpec.contentTopPadding;
62928
- const shiftX = minLeft < 0 ? minLeft : 0;
62929
- const shiftY = minTop < topPadding ? minTop - topPadding : 0;
63090
+ const spacing = resolveBoxSpacing(containerSpec.style, 0);
63091
+ const minInsetX = spacing.marginLeft + spacing.paddingLeft;
63092
+ const minInsetY = spacing.marginTop + spacing.paddingTop;
63093
+ const shiftX = minLeft < minInsetX ? minLeft - minInsetX : 0;
63094
+ const shiftY = minTop < minInsetY ? minTop - minInsetY : 0;
62930
63095
  const baseWidth = containerSize.width - shiftX;
62931
63096
  const baseHeight = containerSize.height - shiftY;
62932
63097
  const neededWidth = Math.ceil(Math.max(baseWidth, maxRight - shiftX));
@@ -62938,7 +63103,8 @@
62938
63103
  childIds,
62939
63104
  containerBBox: toPlainRect(containerBBox),
62940
63105
  containerSize: toPlainRect({ x: 0, y: 0, ...containerSize }),
62941
- topPadding,
63106
+ minInsetX,
63107
+ minInsetY,
62942
63108
  bounds: { minLeft, minTop, maxRight, maxBottom },
62943
63109
  shift: { shiftX, shiftY },
62944
63110
  childDebug,
@@ -62957,6 +63123,7 @@
62957
63123
  to: { width: neededWidth, height: neededHeight }
62958
63124
  });
62959
63125
  container.resize(neededWidth, neededHeight);
63126
+ syncOwnedPortPositions(containerId);
62960
63127
  } else {
62961
63128
  logResize("container-no-resize", { containerId });
62962
63129
  }
@@ -63027,100 +63194,138 @@
63027
63194
  if (!isPrimaryMover(node, options)) return;
63028
63195
  growAncestorContainers(node);
63029
63196
  });
63197
+ const syncOwnedPortPositions = (ownerId) => {
63198
+ const owner = graphView.getCellById(ownerId);
63199
+ if (!owner || typeof owner.size !== "function" || typeof owner.getPorts !== "function" || typeof owner.setPortProp !== "function") {
63200
+ return;
63201
+ }
63202
+ const size = owner.size();
63203
+ const portIds = owner.getPorts().map((port2) => String(port2?.id ?? "")).filter((portId) => portId.length > 0);
63204
+ for (const portId of portIds) {
63205
+ const placement = portPlacementById.get(portId) ?? { side: "right", ratio: 0.5 };
63206
+ const ratio2 = clamp3(placement.ratio, 0.05, 0.95);
63207
+ const nextArgs = placement.side === "left" || placement.side === "right" ? {
63208
+ x: placement.side === "left" ? 0 : size.width,
63209
+ y: ratio2 * size.height,
63210
+ side: placement.side,
63211
+ ratio: ratio2
63212
+ } : {
63213
+ x: ratio2 * size.width,
63214
+ y: placement.side === "top" ? 0 : size.height,
63215
+ side: placement.side,
63216
+ ratio: ratio2
63217
+ };
63218
+ owner.setPortProp(portId, {
63219
+ args: nextArgs,
63220
+ label: {
63221
+ position: resolveBorderPortLabelPosition(placement.side)
63222
+ }
63223
+ });
63224
+ }
63225
+ };
63226
+ graphView.on("node:resizing", ({ node }) => {
63227
+ syncOwnedPortPositions(String(node?.id ?? ""));
63228
+ });
63229
+ graphView.on("node:resized", ({ node, options }) => {
63230
+ if (options?.silent) return;
63231
+ syncOwnedPortPositions(String(node?.id ?? ""));
63232
+ growAncestorContainers(node);
63233
+ });
63030
63234
  const edgeLabelPosition = (placement) => {
63031
63235
  if (placement === "begin") return 0.15;
63032
63236
  if (placement === "end") return 0.85;
63033
63237
  return 0.5;
63034
63238
  };
63035
- const endpointOwnerId = (nodeId) => {
63036
- const node = nodeById.get(nodeId);
63037
- if (!node) return void 0;
63038
- return node.kind === "Port" ? node.parentId : node.id;
63039
- };
63040
- const undirectedPairKey = (a, b) => a < b ? `${a}<->${b}` : `${b}<->${a}`;
63041
- const edgeById = new Map(graph.edges.map((edge) => [edge.id, edge]));
63239
+ const directedPairKey = (sourceId, targetId) => `${sourceId}=>${targetId}`;
63240
+ const undirectedPairKey = (leftId, rightId) => leftId < rightId ? `${leftId}<=>${rightId}` : `${rightId}<=>${leftId}`;
63241
+ const edgeIdsByPair = /* @__PURE__ */ new Map();
63042
63242
  const undirectedEdgeIdsByPair = /* @__PURE__ */ new Map();
63043
63243
  for (const edge of graph.edges) {
63044
- const sourceOwner = endpointOwnerId(edge.sourceId);
63045
- const targetOwner = endpointOwnerId(edge.targetId);
63046
- if (!sourceOwner || !targetOwner || sourceOwner === targetOwner) continue;
63047
- const key = undirectedPairKey(sourceOwner, targetOwner);
63048
- const ids = undirectedEdgeIdsByPair.get(key) ?? [];
63244
+ if (!edge.sourceId || !edge.targetId) continue;
63245
+ const key = directedPairKey(edge.sourceId, edge.targetId);
63246
+ const ids = edgeIdsByPair.get(key) ?? [];
63049
63247
  ids.push(edge.id);
63050
- undirectedEdgeIdsByPair.set(key, ids);
63051
- }
63052
- const fanningVertexForEdge = (edge) => {
63053
- const sourceOwner = endpointOwnerId(edge.sourceId);
63054
- const targetOwner = endpointOwnerId(edge.targetId);
63055
- if (!sourceOwner || !targetOwner || sourceOwner === targetOwner) {
63248
+ edgeIdsByPair.set(key, ids);
63249
+ const undirectedKey = undirectedPairKey(edge.sourceId, edge.targetId);
63250
+ const pairIds = undirectedEdgeIdsByPair.get(undirectedKey) ?? [];
63251
+ pairIds.push(edge.id);
63252
+ undirectedEdgeIdsByPair.set(undirectedKey, pairIds);
63253
+ }
63254
+ const edgePointForEndpoint = (endpointId) => {
63255
+ const endpoint = nodeById.get(endpointId);
63256
+ if (!endpoint) {
63056
63257
  return void 0;
63057
63258
  }
63058
- const edgeIds = undirectedEdgeIdsByPair.get(undirectedPairKey(sourceOwner, targetOwner)) ?? [];
63059
- if (edgeIds.length <= 1) {
63259
+ if (endpoint.kind === "Port" && endpoint.parentId) {
63260
+ const ownerBox = layout.boxes.get(endpoint.parentId);
63261
+ const placement = portPlacementById.get(endpoint.id);
63262
+ if (!ownerBox || !placement) {
63263
+ return void 0;
63264
+ }
63265
+ const ratio2 = clamp3(placement.ratio, 0.05, 0.95);
63266
+ if (placement.side === "left") {
63267
+ return { x: ownerBox.x, y: ownerBox.y + ownerBox.height * ratio2 };
63268
+ }
63269
+ if (placement.side === "right") {
63270
+ return { x: ownerBox.x + ownerBox.width, y: ownerBox.y + ownerBox.height * ratio2 };
63271
+ }
63272
+ if (placement.side === "top") {
63273
+ return { x: ownerBox.x + ownerBox.width * ratio2, y: ownerBox.y };
63274
+ }
63275
+ return { x: ownerBox.x + ownerBox.width * ratio2, y: ownerBox.y + ownerBox.height };
63276
+ }
63277
+ const box = layout.boxes.get(endpointId);
63278
+ if (!box) {
63060
63279
  return void 0;
63061
63280
  }
63062
- const orderedEdgeIds = [...edgeIds].sort((leftId, rightId) => {
63063
- const leftEdge = edgeById.get(leftId);
63064
- const rightEdge = edgeById.get(rightId);
63065
- if (!leftEdge || !rightEdge) return leftId.localeCompare(rightId);
63066
- const leftSource = endpointOwnerId(leftEdge.sourceId);
63067
- const leftTarget = endpointOwnerId(leftEdge.targetId);
63068
- const rightSource = endpointOwnerId(rightEdge.sourceId);
63069
- const rightTarget = endpointOwnerId(rightEdge.targetId);
63070
- const leftForward = leftSource && leftTarget ? leftSource < leftTarget ? 0 : 1 : 0;
63071
- const rightForward = rightSource && rightTarget ? rightSource < rightTarget ? 0 : 1 : 0;
63072
- if (leftForward !== rightForward) return leftForward - rightForward;
63073
- return leftId.localeCompare(rightId);
63074
- });
63075
- const [pairA, pairB] = sourceOwner < targetOwner ? [sourceOwner, targetOwner] : [targetOwner, sourceOwner];
63076
- const forwardIds = [];
63077
- const reverseIds = [];
63078
- for (const edgeId of orderedEdgeIds) {
63079
- const pairEdge = edgeById.get(edgeId);
63080
- if (!pairEdge) continue;
63081
- const pairSource = endpointOwnerId(pairEdge.sourceId);
63082
- const pairTarget = endpointOwnerId(pairEdge.targetId);
63083
- if (pairSource === pairA && pairTarget === pairB) {
63084
- forwardIds.push(edgeId);
63085
- } else if (pairSource === pairB && pairTarget === pairA) {
63086
- reverseIds.push(edgeId);
63087
- }
63088
- }
63089
- if (forwardIds.length === 0 || reverseIds.length === 0) {
63281
+ return { x: box.x + box.width / 2, y: box.y + box.height / 2 };
63282
+ };
63283
+ const fanningVertexForEdge = (edge) => {
63284
+ if (!edge.sourceId || !edge.targetId) {
63090
63285
  return void 0;
63091
63286
  }
63092
- let directionSign = 0;
63093
- let laneIndex = 0;
63094
- let laneCount = 0;
63095
- if (sourceOwner === pairA && targetOwner === pairB) {
63096
- directionSign = 1;
63097
- laneIndex = forwardIds.indexOf(edge.id);
63098
- laneCount = forwardIds.length;
63099
- } else if (sourceOwner === pairB && targetOwner === pairA) {
63100
- directionSign = -1;
63101
- laneIndex = reverseIds.indexOf(edge.id);
63102
- laneCount = reverseIds.length;
63103
- }
63104
- if (directionSign === 0 || laneIndex < 0 || laneCount <= 0) {
63287
+ const undirectedIds = undirectedEdgeIdsByPair.get(undirectedPairKey(edge.sourceId, edge.targetId)) ?? [];
63288
+ if (undirectedIds.length <= 1) {
63105
63289
  return void 0;
63106
63290
  }
63107
- const pairABox = layout.boxes.get(pairA);
63108
- const pairBBox = layout.boxes.get(pairB);
63109
- if (!pairABox || !pairBBox) {
63291
+ const sourcePoint = edgePointForEndpoint(edge.sourceId);
63292
+ const targetPoint = edgePointForEndpoint(edge.targetId);
63293
+ if (!sourcePoint || !targetPoint) {
63110
63294
  return void 0;
63111
63295
  }
63112
- const sx = pairABox.x + pairABox.width / 2;
63113
- const sy = pairABox.y + pairABox.height / 2;
63114
- const tx = pairBBox.x + pairBBox.width / 2;
63115
- const ty = pairBBox.y + pairBBox.height / 2;
63296
+ const sx = sourcePoint.x;
63297
+ const sy = sourcePoint.y;
63298
+ const tx = targetPoint.x;
63299
+ const ty = targetPoint.y;
63116
63300
  const dx = tx - sx;
63117
63301
  const dy = ty - sy;
63118
63302
  const len = Math.hypot(dx, dy);
63119
63303
  if (!Number.isFinite(len) || len < 1) {
63120
63304
  return void 0;
63121
63305
  }
63122
- const laneOffset = laneIndex - (laneCount - 1) / 2;
63123
- const offset4 = directionSign * 16 + laneOffset * 10;
63306
+ const forwardIds = (edgeIdsByPair.get(directedPairKey(edge.sourceId, edge.targetId)) ?? []).slice().sort((left4, right4) => left4.localeCompare(right4));
63307
+ const reverseIds = (edgeIdsByPair.get(directedPairKey(edge.targetId, edge.sourceId)) ?? []).slice().sort((left4, right4) => left4.localeCompare(right4));
63308
+ let offset4 = 0;
63309
+ if (forwardIds.length > 0 && reverseIds.length > 0) {
63310
+ const forwardIndex = forwardIds.indexOf(edge.id);
63311
+ const reverseIndex = reverseIds.indexOf(edge.id);
63312
+ if (forwardIndex >= 0) {
63313
+ const laneOffset = forwardIndex - (forwardIds.length - 1) / 2;
63314
+ offset4 = 16 + laneOffset * 10;
63315
+ } else if (reverseIndex >= 0) {
63316
+ const laneOffset = reverseIndex - (reverseIds.length - 1) / 2;
63317
+ offset4 = -16 + laneOffset * 10;
63318
+ } else {
63319
+ return void 0;
63320
+ }
63321
+ } else {
63322
+ const laneIndex = forwardIds.indexOf(edge.id);
63323
+ if (laneIndex < 0 || forwardIds.length <= 1) {
63324
+ return void 0;
63325
+ }
63326
+ const laneOffset = laneIndex - (forwardIds.length - 1) / 2;
63327
+ offset4 = laneOffset * 16;
63328
+ }
63124
63329
  if (Math.abs(offset4) < 0.01) {
63125
63330
  return void 0;
63126
63331
  }
@@ -63155,8 +63360,8 @@
63155
63360
  id: edge.id,
63156
63361
  source: resolveEndpoint(edge.sourceId),
63157
63362
  target: resolveEndpoint(edge.targetId),
63158
- router: { name: "normal" },
63159
- connector: { name: "rounded" },
63363
+ router: resolveEdgeRouter(resolvedStyle),
63364
+ connector: resolveEdgeConnector(resolvedStyle),
63160
63365
  attrs: {
63161
63366
  line: lineAttrs
63162
63367
  },
@@ -63173,7 +63378,7 @@
63173
63378
  labelBody: resolveEdgeLabelBodyAttrs(resolvedStyle, label.placement)
63174
63379
  }
63175
63380
  })),
63176
- zIndex: 5
63381
+ zIndex: 50
63177
63382
  });
63178
63383
  }
63179
63384
  const selectCompartmentParent = (node) => {
@@ -63210,49 +63415,92 @@
63210
63415
  e.preventDefault();
63211
63416
  e.stopPropagation();
63212
63417
  });
63213
- const clamp3 = (value, min, max2) => Math.max(min, Math.min(max2, value));
63418
+ const sidePriority = (side) => {
63419
+ switch (side) {
63420
+ case "left":
63421
+ return 0;
63422
+ case "top":
63423
+ return 1;
63424
+ case "right":
63425
+ return 2;
63426
+ case "bottom":
63427
+ return 3;
63428
+ }
63429
+ };
63430
+ const projectPortPlacement = (node, clientX, clientY, preferredSide) => {
63431
+ if (!node || typeof node.size !== "function") return void 0;
63432
+ const size = node.size();
63433
+ const position2 = typeof node.getPosition === "function" ? node.getPosition() : { x: Number(node?.position?.x ?? 0), y: Number(node?.position?.y ?? 0) };
63434
+ const localPoint = typeof graphView.clientToGraph === "function" ? graphView.clientToGraph(clientX, clientY) : { x: clientX, y: clientY };
63435
+ const localX = clamp3(localPoint.x - position2.x, 0, size.width);
63436
+ const localY = clamp3(localPoint.y - position2.y, 0, size.height);
63437
+ const candidates = [
63438
+ { side: "left", distance: localX },
63439
+ { side: "right", distance: size.width - localX },
63440
+ { side: "top", distance: localY },
63441
+ { side: "bottom", distance: size.height - localY }
63442
+ ];
63443
+ candidates.sort((left4, right4) => left4.distance - right4.distance || sidePriority(left4.side) - sidePriority(right4.side));
63444
+ const closest2 = candidates[0];
63445
+ const preferredCandidate = preferredSide ? candidates.find((candidate) => candidate.side === preferredSide) : void 0;
63446
+ const hysteresis = 8;
63447
+ const side = preferredCandidate && preferredSide && preferredCandidate.distance - closest2.distance <= hysteresis ? preferredSide : closest2.side;
63448
+ const ratio2 = side === "left" || side === "right" ? size.height > 0 ? localY / size.height : 0.5 : size.width > 0 ? localX / size.width : 0.5;
63449
+ return {
63450
+ side,
63451
+ ratio: Math.max(0.05, Math.min(0.95, ratio2))
63452
+ };
63453
+ };
63214
63454
  const onPointerMove = (event) => {
63215
- if (!activePortDrag) return;
63455
+ if (!activePortDrag || event.pointerId !== activePortDrag.pointerId) return;
63216
63456
  const node = graphView.getCellById(activePortDrag.nodeId);
63217
63457
  if (!node || typeof node.size !== "function" || typeof node.getPort !== "function" || typeof node.setPortProp !== "function") return;
63458
+ const placement = projectPortPlacement(node, event.clientX, event.clientY, activePortDrag.side);
63459
+ if (!placement) return;
63460
+ if (placement.side === activePortDrag.side && Math.abs(placement.ratio - activePortDrag.ratio) < 5e-3) {
63461
+ return;
63462
+ }
63218
63463
  const size = node.size();
63219
- const deltaY = event.clientY - activePortDrag.startClientY;
63220
- const nextY = clamp3(activePortDrag.startPortY + deltaY, 4, Math.max(4, size.height - 4));
63464
+ const nextX = placement.side === "left" ? 0 : placement.side === "right" ? size.width : placement.ratio * size.width;
63465
+ const nextY = placement.side === "top" ? 0 : placement.side === "bottom" ? size.height : placement.ratio * size.height;
63466
+ node.setPortProp(activePortDrag.portId, "args/x", nextX);
63221
63467
  node.setPortProp(activePortDrag.portId, "args/y", nextY);
63468
+ node.setPortProp(activePortDrag.portId, "args/side", placement.side);
63469
+ node.setPortProp(activePortDrag.portId, "args/ratio", placement.ratio);
63470
+ if (placement.side !== activePortDrag.side) {
63471
+ node.setPortProp(activePortDrag.portId, "label/position", resolveBorderPortLabelPosition(placement.side));
63472
+ }
63473
+ activePortDrag.side = placement.side;
63474
+ activePortDrag.ratio = placement.ratio;
63475
+ portPlacementById.set(activePortDrag.portId, placement);
63222
63476
  };
63223
- const onPointerUp = () => {
63477
+ const onPointerUp = (event) => {
63478
+ if (!activePortDrag || event.pointerId !== activePortDrag.pointerId) return;
63479
+ if (typeof activePortDrag.container.releasePointerCapture === "function") {
63480
+ try {
63481
+ activePortDrag.container.releasePointerCapture(activePortDrag.pointerId);
63482
+ } catch {
63483
+ }
63484
+ }
63224
63485
  activePortDrag = void 0;
63225
63486
  };
63226
63487
  window.addEventListener("pointermove", onPointerMove);
63227
63488
  window.addEventListener("pointerup", onPointerUp);
63228
- const startPortDrag = (clientY, node, portId) => {
63489
+ const startPortDrag = (clientX, clientY, node, portId, pointerId, container) => {
63229
63490
  if (!node || typeof node.getPort !== "function") return;
63230
- const existing = node.getPort(portId);
63231
- const startPortY = typeof existing?.args?.y === "number" ? existing.args.y : node.size().height / 2;
63491
+ const placement = projectPortPlacement(node, clientX, clientY, portPlacementById.get(portId)?.side ?? "right") ?? portPlacementById.get(portId) ?? { side: "right", ratio: 0.5 };
63232
63492
  activePortDrag = {
63233
63493
  nodeId: String(node.id),
63234
63494
  portId: String(portId),
63235
- startClientY: clientY,
63236
- startPortY
63495
+ pointerId,
63496
+ side: placement.side,
63497
+ ratio: placement.ratio,
63498
+ container
63237
63499
  };
63238
63500
  };
63239
- const onCanvasPointerDown = (event) => {
63240
- if (!isPortPointerTarget(event)) return;
63241
- const portId = findPortIdFromTarget(event.target);
63242
- if (!portId) return;
63243
- const ownerId = ownerByPortId.get(portId);
63244
- if (!ownerId) return;
63245
- const node = graphView.getCellById(ownerId);
63246
- startPortDrag(event.clientY, node, portId);
63247
- event.preventDefault();
63248
- event.stopPropagation();
63249
- if (typeof event.stopImmediatePropagation === "function") event.stopImmediatePropagation();
63250
- };
63251
- liveCanvas.addEventListener("pointerdown", onCanvasPointerDown, true);
63252
63501
  liveCanvas.addEventListener("DOMNodeRemovedFromDocument", () => {
63253
63502
  window.removeEventListener("pointermove", onPointerMove);
63254
63503
  window.removeEventListener("pointerup", onPointerUp);
63255
- liveCanvas.removeEventListener("pointerdown", onCanvasPointerDown, true);
63256
63504
  }, { once: true });
63257
63505
  const resultContainer = liveCanvas.closest(".oml-md-result");
63258
63506
  const resizeObserver = new ResizeObserver(() => {
@@ -63613,7 +63861,7 @@ ${serialized}`;
63613
63861
  dagreLib = mod3.default ?? mod3;
63614
63862
  return dagreLib;
63615
63863
  }
63616
- async function layoutGraphDagre(graph, options) {
63864
+ async function layoutGraphDagre(graph, options, rootSpacing) {
63617
63865
  const dagre = await loadDagreLib();
63618
63866
  const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
63619
63867
  const layoutNodes = graph.nodes.filter((node) => node.kind !== "Port");
@@ -63648,8 +63896,6 @@ ${serialized}`;
63648
63896
  stack: {
63649
63897
  direction: styleLayout.direction === "horizontal" ? "horizontal" : "vertical",
63650
63898
  gap: clampLayoutNumber(styleLayout.gap, 0, 400, 0),
63651
- marginx: clampLayoutNumber(styleLayout.marginx, 0, 200, 0),
63652
- marginy: clampLayoutNumber(styleLayout.marginy, 0, 200, 0),
63653
63899
  stretch: typeof styleLayout.stretch === "boolean" ? styleLayout.stretch : true
63654
63900
  }
63655
63901
  };
@@ -63664,29 +63910,40 @@ ${serialized}`;
63664
63910
  dagre: {
63665
63911
  rankdir,
63666
63912
  nodesep: clampLayoutNumber(styleLayout.nodesep, 0, 400, options.nodesep),
63667
- ranksep: clampLayoutNumber(styleLayout.ranksep, 0, 500, options.ranksep),
63668
- marginx: clampLayoutNumber(styleLayout.marginx, 0, 200, options.marginx),
63669
- marginy: clampLayoutNumber(styleLayout.marginy, 0, 200, options.marginy)
63913
+ ranksep: clampLayoutNumber(styleLayout.ranksep, 0, 500, options.ranksep)
63670
63914
  }
63671
63915
  };
63672
63916
  };
63673
63917
  const childPositionById = /* @__PURE__ */ new Map();
63918
+ const branchChildForParent = (parentId, nodeId) => {
63919
+ let cursorId = nodeId;
63920
+ while (cursorId) {
63921
+ const node = nodeById.get(cursorId);
63922
+ if (!node) {
63923
+ return void 0;
63924
+ }
63925
+ if (node.parentId === parentId) {
63926
+ return node.id;
63927
+ }
63928
+ cursorId = node.parentId;
63929
+ }
63930
+ return void 0;
63931
+ };
63674
63932
  const edgesBetweenChildren = (parentId, childSet) => {
63675
63933
  const seen = /* @__PURE__ */ new Set();
63676
63934
  const links = [];
63677
63935
  for (const edge of graph.edges) {
63678
63936
  const sourceId = endpointOwner(edge.sourceId);
63679
63937
  const targetId = endpointOwner(edge.targetId);
63680
- if (!sourceId || !targetId || sourceId === targetId) continue;
63681
- if (!childSet.has(sourceId) || !childSet.has(targetId)) continue;
63682
- const sourceNode = nodeById.get(sourceId);
63683
- const targetNode = nodeById.get(targetId);
63684
- if (!sourceNode || !targetNode) continue;
63685
- if (sourceNode.parentId !== parentId || targetNode.parentId !== parentId) continue;
63686
- const key = `${sourceId}=>${targetId}`;
63938
+ if (!sourceId || !targetId) continue;
63939
+ const sourceBranchId = branchChildForParent(parentId, sourceId);
63940
+ const targetBranchId = branchChildForParent(parentId, targetId);
63941
+ if (!sourceBranchId || !targetBranchId || sourceBranchId === targetBranchId) continue;
63942
+ if (!childSet.has(sourceBranchId) || !childSet.has(targetBranchId)) continue;
63943
+ const key = `${sourceBranchId}=>${targetBranchId}`;
63687
63944
  if (seen.has(key)) continue;
63688
63945
  seen.add(key);
63689
- links.push({ source: sourceId, target: targetId });
63946
+ links.push({ source: sourceBranchId, target: targetBranchId });
63690
63947
  }
63691
63948
  return links;
63692
63949
  };
@@ -63699,8 +63956,8 @@ ${serialized}`;
63699
63956
  return { width: 0, height: 0 };
63700
63957
  }
63701
63958
  const parent = parentId ? nodeById.get(parentId) : void 0;
63702
- const topPadding = parent?.contentTopPadding ?? 0;
63703
63959
  const localLayout = layoutOptionsForParent(parentId);
63960
+ const spacing = parent ? resolveBoxSpacing(parent.style, 0) : rootSpacing;
63704
63961
  let totalWidth = 0;
63705
63962
  let totalHeight = 0;
63706
63963
  if (localLayout.type === "stack") {
@@ -63708,13 +63965,13 @@ ${serialized}`;
63708
63965
  const childNodes = childIds.map((childId) => nodeById.get(childId)).filter((node) => !!node);
63709
63966
  const maxChildWidth = childNodes.reduce((max2, child) => Math.max(max2, child.width), 0);
63710
63967
  const maxChildHeight = childNodes.reduce((max2, child) => Math.max(max2, child.height), 0);
63711
- const parentContentWidth = Math.max(0, (parent?.width ?? 0) - local.marginx * 2);
63712
- const parentContentHeight = Math.max(0, (parent?.height ?? 0) - topPadding - local.marginy * 2);
63968
+ const parentContentWidth = Math.max(0, (parent?.width ?? 0) - spacing.marginLeft - spacing.marginRight - spacing.paddingLeft - spacing.paddingRight);
63969
+ const parentContentHeight = Math.max(0, (parent?.height ?? 0) - spacing.marginTop - spacing.marginBottom - spacing.paddingTop - spacing.paddingBottom);
63713
63970
  const availableWidth = Math.max(maxChildWidth, parentContentWidth);
63714
63971
  const availableHeight = Math.max(maxChildHeight, parentContentHeight);
63715
63972
  const isSingleChild = childNodes.length === 1;
63716
- let cursorX = local.marginx;
63717
- let cursorY = topPadding + local.marginy;
63973
+ let cursorX = spacing.marginLeft + spacing.paddingLeft;
63974
+ let cursorY = spacing.marginTop + spacing.paddingTop;
63718
63975
  for (const child of childNodes) {
63719
63976
  if (local.stretch && isSingleChild) {
63720
63977
  child.width = Math.max(0, availableWidth);
@@ -63737,16 +63994,16 @@ ${serialized}`;
63737
63994
  childNodes.reduce((sum, child) => sum + child.height, 0) + Math.max(0, childNodes.length - 1) * local.gap
63738
63995
  );
63739
63996
  const contentWidth = Math.max(0, childNodes.reduce((max2, child) => Math.max(max2, child.width), 0));
63740
- totalWidth = local.marginx * 2 + contentWidth;
63741
- totalHeight = topPadding + local.marginy * 2 + contentHeight;
63997
+ totalWidth = spacing.marginLeft + spacing.paddingLeft + contentWidth + spacing.paddingRight + spacing.marginRight;
63998
+ totalHeight = spacing.marginTop + spacing.paddingTop + contentHeight + spacing.paddingBottom + spacing.marginBottom;
63742
63999
  } else {
63743
64000
  const contentWidth = Math.max(
63744
64001
  0,
63745
64002
  childNodes.reduce((sum, child) => sum + child.width, 0) + Math.max(0, childNodes.length - 1) * local.gap
63746
64003
  );
63747
64004
  const contentHeight = Math.max(0, childNodes.reduce((max2, child) => Math.max(max2, child.height), 0));
63748
- totalWidth = local.marginx * 2 + contentWidth;
63749
- totalHeight = topPadding + local.marginy * 2 + contentHeight;
64005
+ totalWidth = spacing.marginLeft + spacing.paddingLeft + contentWidth + spacing.paddingRight + spacing.marginRight;
64006
+ totalHeight = spacing.marginTop + spacing.paddingTop + contentHeight + spacing.paddingBottom + spacing.marginBottom;
63750
64007
  }
63751
64008
  } else {
63752
64009
  const localOptions = localLayout.dagre;
@@ -63800,22 +64057,18 @@ ${serialized}`;
63800
64057
  const left4 = laid.x - width2 / 2;
63801
64058
  const top4 = laid.y - height2 / 2;
63802
64059
  childPositionById.set(childId, {
63803
- x: localOptions.marginx + (left4 - minX),
63804
- y: topPadding + localOptions.marginy + (top4 - minY)
64060
+ x: spacing.marginLeft + spacing.paddingLeft + (left4 - minX),
64061
+ y: spacing.marginTop + spacing.paddingTop + (top4 - minY)
63805
64062
  });
63806
64063
  }
63807
64064
  const contentWidth = Math.max(0, maxX - minX);
63808
64065
  const contentHeight = Math.max(0, maxY - minY);
63809
- totalWidth = localOptions.marginx * 2 + contentWidth;
63810
- totalHeight = topPadding + localOptions.marginy * 2 + contentHeight;
64066
+ totalWidth = spacing.marginLeft + spacing.paddingLeft + contentWidth + spacing.paddingRight + spacing.marginRight;
64067
+ totalHeight = spacing.marginTop + spacing.paddingTop + contentHeight + spacing.paddingBottom + spacing.marginBottom;
63811
64068
  }
63812
64069
  if (parent) {
63813
- if (!parent.hasExplicitWidth) {
63814
- parent.width = Math.max(parent.width, totalWidth);
63815
- }
63816
- if (!parent.hasExplicitHeight) {
63817
- parent.height = Math.max(parent.height, totalHeight);
63818
- }
64070
+ parent.width = Math.max(parent.width, totalWidth);
64071
+ parent.height = Math.max(parent.height, totalHeight);
63819
64072
  }
63820
64073
  return { width: totalWidth, height: totalHeight };
63821
64074
  };
@@ -63833,9 +64086,9 @@ ${serialized}`;
63833
64086
  if (localLayout.type === "stack") {
63834
64087
  const local = localLayout.stack;
63835
64088
  const childNodes = childIds.map((childId) => nodeById.get(childId)).filter((node) => !!node);
63836
- const topPadding = parent.contentTopPadding;
63837
- const contentWidth = Math.max(0, parent.width - local.marginx * 2);
63838
- const contentHeight = Math.max(0, parent.height - topPadding - local.marginy * 2);
64089
+ const spacing = resolveBoxSpacing(parent.style, 0);
64090
+ const contentWidth = Math.max(0, parent.width - spacing.marginLeft - spacing.marginRight - spacing.paddingLeft - spacing.paddingRight);
64091
+ const contentHeight = Math.max(0, parent.height - spacing.marginTop - spacing.marginBottom - spacing.paddingTop - spacing.paddingBottom);
63839
64092
  const isSingleChild = childNodes.length === 1;
63840
64093
  if (local.stretch) {
63841
64094
  for (const child of childNodes) {
@@ -63851,8 +64104,8 @@ ${serialized}`;
63851
64104
  }
63852
64105
  }
63853
64106
  }
63854
- let cursorX = local.marginx;
63855
- let cursorY = topPadding + local.marginy;
64107
+ let cursorX = spacing.marginLeft + spacing.paddingLeft;
64108
+ let cursorY = spacing.marginTop + spacing.paddingTop;
63856
64109
  for (const child of childNodes) {
63857
64110
  childPositionById.set(child.id, { x: cursorX, y: cursorY });
63858
64111
  if (local.direction === "vertical") {
@@ -64041,10 +64294,7 @@ ${serialized}`;
64041
64294
  style: {},
64042
64295
  children: [],
64043
64296
  width: 160,
64044
- height: 70,
64045
- hasExplicitWidth: false,
64046
- hasExplicitHeight: false,
64047
- contentTopPadding: 0
64297
+ height: 70
64048
64298
  });
64049
64299
  }
64050
64300
  const validParent = (child, parent) => {
@@ -64114,9 +64364,6 @@ ${serialized}`;
64114
64364
  const estimated = estimateSize(node.kind, baseLabel, node.labels.length, node.style);
64115
64365
  node.width = estimated.width;
64116
64366
  node.height = estimated.height;
64117
- node.hasExplicitWidth = estimated.hasExplicitWidth;
64118
- node.hasExplicitHeight = estimated.hasExplicitHeight;
64119
- node.contentTopPadding = resolveContainerTopPadding(node, baseLabel);
64120
64367
  }
64121
64368
  for (const edge of edges) {
64122
64369
  edge.style = styleFor("edge", edge.id, edge.classes, edge.properties);
@@ -64138,57 +64385,40 @@ ${serialized}`;
64138
64385
  function estimateSize(kind, label, labelCount, style2) {
64139
64386
  const styledWidth = toPositiveNumber(style2.width);
64140
64387
  const styledHeight = toPositiveNumber(style2.height);
64141
- if (styledWidth && styledHeight) {
64142
- return { width: styledWidth, height: styledHeight, hasExplicitWidth: true, hasExplicitHeight: true };
64143
- }
64388
+ const attrs = extractStyleAttrs(style2);
64389
+ const labelAttrs = asRecord(attrs.label);
64390
+ const labelDisplay = typeof labelAttrs?.display === "string" ? labelAttrs.display.trim().toLowerCase() : "";
64391
+ const labelOpacity = toNonNegativeNumber(labelAttrs?.opacity);
64392
+ const labelVisible = labelDisplay !== "none" && (labelOpacity === void 0 || labelOpacity > 0);
64393
+ const effectiveLabel = labelVisible ? label : "";
64394
+ const effectiveLabelCount = labelVisible ? labelCount : 0;
64144
64395
  if (kind === "Port") {
64145
64396
  return {
64146
- width: styledWidth ?? 14,
64147
- height: styledHeight ?? 14,
64148
- hasExplicitWidth: styledWidth !== void 0,
64149
- hasExplicitHeight: styledHeight !== void 0
64397
+ width: Math.max(14, styledWidth ?? 0),
64398
+ height: Math.max(14, styledHeight ?? 0)
64150
64399
  };
64151
64400
  }
64152
- const textWidth = Math.max(24, label.length * 7);
64153
- const baseWidth = Math.max(72, Math.min(280, textWidth + 26));
64401
+ const textWidth = effectiveLabel.length > 0 ? Math.max(24, effectiveLabel.length * 7) : 0;
64402
+ const baseWidth = effectiveLabel.length > 0 ? Math.max(72, Math.min(280, textWidth + 26)) : 0;
64154
64403
  if (kind === "Compartment") {
64155
- const size2 = { width: baseWidth, height: Math.max(44, 24 + labelCount * 16) };
64404
+ const size2 = {
64405
+ width: baseWidth,
64406
+ height: effectiveLabelCount > 0 ? Math.max(44, 24 + effectiveLabelCount * 16) : 0
64407
+ };
64156
64408
  return {
64157
- width: styledWidth ?? size2.width,
64158
- height: styledHeight ?? size2.height,
64159
- hasExplicitWidth: styledWidth !== void 0,
64160
- hasExplicitHeight: styledHeight !== void 0
64409
+ width: Math.max(size2.width, styledWidth ?? 0),
64410
+ height: Math.max(size2.height, styledHeight ?? 0)
64161
64411
  };
64162
64412
  }
64163
- const size = { width: baseWidth, height: Math.max(36, 20 + labelCount * 16) };
64413
+ const size = {
64414
+ width: baseWidth,
64415
+ height: effectiveLabelCount > 0 ? Math.max(36, 20 + effectiveLabelCount * 16) : 0
64416
+ };
64164
64417
  return {
64165
- width: styledWidth ?? size.width,
64166
- height: styledHeight ?? size.height,
64167
- hasExplicitWidth: styledWidth !== void 0,
64168
- hasExplicitHeight: styledHeight !== void 0
64418
+ width: Math.max(size.width, styledWidth ?? 0),
64419
+ height: Math.max(size.height, styledHeight ?? 0)
64169
64420
  };
64170
64421
  }
64171
- function resolveContainerTopPadding(node, labelText) {
64172
- if (node.children.length === 0) {
64173
- return 0;
64174
- }
64175
- const explicitPadding = toNonNegativeNumber(node.style.paddingTop);
64176
- if (explicitPadding !== void 0) {
64177
- return Math.ceil(explicitPadding);
64178
- }
64179
- const attrs = extractStyleAttrs(node.style);
64180
- const label = asRecord(attrs.label);
64181
- if (label?.display === "none") {
64182
- return 0;
64183
- }
64184
- const labelOpacity = toNonNegativeNumber(label?.opacity);
64185
- if (labelOpacity !== void 0 && labelOpacity <= 0) {
64186
- return 0;
64187
- }
64188
- const fontSize = toPositiveNumber(label?.fontSize) ?? 12;
64189
- const lineCount = Math.max(1, labelText.split("\n").filter((line2) => line2.trim().length > 0).length);
64190
- return Math.ceil(fontSize * 1.2 * lineCount);
64191
- }
64192
64422
  function localName(value) {
64193
64423
  return displayLabelFromIri(value);
64194
64424
  }
@@ -64236,9 +64466,7 @@ ${serialized}`;
64236
64466
  const rankdir = rankdirRaw === "LR" || rankdirRaw === "RL" || rankdirRaw === "TB" || rankdirRaw === "BT" ? rankdirRaw : "LR";
64237
64467
  const nodesep = clampLayoutNumber(layout.nodesep, 0, 400, 28);
64238
64468
  const ranksep = clampLayoutNumber(layout.ranksep, 0, 500, 64);
64239
- const marginx = clampLayoutNumber(layout.marginx, 0, 200, 16);
64240
- const marginy = clampLayoutNumber(layout.marginy, 0, 200, 16);
64241
- return { rankdir, nodesep, ranksep, marginx, marginy };
64469
+ return { rankdir, nodesep, ranksep };
64242
64470
  }
64243
64471
  function clampLayoutNumber(value, min, max2, fallback) {
64244
64472
  if (typeof value !== "number" || !Number.isFinite(value)) {
@@ -64246,6 +64474,111 @@ ${serialized}`;
64246
64474
  }
64247
64475
  return Math.max(min, Math.min(max2, Math.round(value)));
64248
64476
  }
64477
+ function resolveRootSpacing(options) {
64478
+ return readBoxSpacing(options, {
64479
+ marginTop: 16,
64480
+ marginBottom: 16,
64481
+ marginLeft: 16,
64482
+ marginRight: 16,
64483
+ paddingTop: 0,
64484
+ paddingBottom: 0,
64485
+ paddingLeft: 0,
64486
+ paddingRight: 0
64487
+ });
64488
+ }
64489
+ function resolveBoxSpacing(style2, fallbackMargin) {
64490
+ return readBoxSpacing(style2, {
64491
+ marginTop: fallbackMargin,
64492
+ marginBottom: fallbackMargin,
64493
+ marginLeft: fallbackMargin,
64494
+ marginRight: fallbackMargin,
64495
+ paddingTop: 0,
64496
+ paddingBottom: 0,
64497
+ paddingLeft: 0,
64498
+ paddingRight: 0
64499
+ });
64500
+ }
64501
+ function readBoxSpacing(style2, defaults7) {
64502
+ const source = style2 ?? {};
64503
+ return {
64504
+ ...readCssBoxSpacing(source.margin, "margin", defaults7),
64505
+ ...readCssBoxSpacing(source.padding, "padding", defaults7)
64506
+ };
64507
+ }
64508
+ function readCssBoxSpacing(value, kind, defaults7) {
64509
+ const sides = parseCssBoxShorthand(value);
64510
+ const isMargin = kind === "margin";
64511
+ if (isMargin) {
64512
+ return {
64513
+ marginTop: sides?.top ?? defaults7.marginTop,
64514
+ marginRight: sides?.right ?? defaults7.marginRight,
64515
+ marginBottom: sides?.bottom ?? defaults7.marginBottom,
64516
+ marginLeft: sides?.left ?? defaults7.marginLeft,
64517
+ paddingTop: defaults7.paddingTop,
64518
+ paddingRight: defaults7.paddingRight,
64519
+ paddingBottom: defaults7.paddingBottom,
64520
+ paddingLeft: defaults7.paddingLeft
64521
+ };
64522
+ }
64523
+ return {
64524
+ marginTop: defaults7.marginTop,
64525
+ marginRight: defaults7.marginRight,
64526
+ marginBottom: defaults7.marginBottom,
64527
+ marginLeft: defaults7.marginLeft,
64528
+ paddingTop: sides?.top ?? defaults7.paddingTop,
64529
+ paddingRight: sides?.right ?? defaults7.paddingRight,
64530
+ paddingBottom: sides?.bottom ?? defaults7.paddingBottom,
64531
+ paddingLeft: sides?.left ?? defaults7.paddingLeft
64532
+ };
64533
+ }
64534
+ function parseCssBoxShorthand(value) {
64535
+ const values = Array.isArray(value) ? value : typeof value === "string" ? value.includes(",") ? void 0 : value.trim().split(/\s+/).filter((token) => token.length > 0) : typeof value === "number" ? [value] : void 0;
64536
+ if (!values || values.length === 0 || values.length > 4) {
64537
+ return void 0;
64538
+ }
64539
+ const parsed = values.map((entry) => {
64540
+ if (typeof entry === "number" && Number.isFinite(entry)) {
64541
+ return entry;
64542
+ }
64543
+ if (typeof entry === "string") {
64544
+ const next = Number.parseFloat(entry);
64545
+ if (Number.isFinite(next)) {
64546
+ return next;
64547
+ }
64548
+ }
64549
+ return void 0;
64550
+ });
64551
+ if (parsed.some((entry) => entry === void 0)) {
64552
+ return void 0;
64553
+ }
64554
+ const clampCssBoxValue = (entry) => {
64555
+ if (entry === void 0) return void 0;
64556
+ return Math.max(0, Math.min(200, Math.round(entry)));
64557
+ };
64558
+ if (parsed.length === 1) {
64559
+ const single = clampCssBoxValue(parsed[0]);
64560
+ return single === void 0 ? void 0 : { top: single, right: single, bottom: single, left: single };
64561
+ }
64562
+ if (parsed.length === 2) {
64563
+ const vertical = clampCssBoxValue(parsed[0]);
64564
+ const horizontal = clampCssBoxValue(parsed[1]);
64565
+ if (vertical === void 0 || horizontal === void 0) return void 0;
64566
+ return { top: vertical, right: horizontal, bottom: vertical, left: horizontal };
64567
+ }
64568
+ if (parsed.length === 3) {
64569
+ const top5 = clampCssBoxValue(parsed[0]);
64570
+ const horizontal = clampCssBoxValue(parsed[1]);
64571
+ const bottom5 = clampCssBoxValue(parsed[2]);
64572
+ if (top5 === void 0 || horizontal === void 0 || bottom5 === void 0) return void 0;
64573
+ return { top: top5, right: horizontal, bottom: bottom5, left: horizontal };
64574
+ }
64575
+ const top4 = clampCssBoxValue(parsed[0]);
64576
+ const right4 = clampCssBoxValue(parsed[1]);
64577
+ const bottom4 = clampCssBoxValue(parsed[2]);
64578
+ const left4 = clampCssBoxValue(parsed[3]);
64579
+ if (top4 === void 0 || right4 === void 0 || bottom4 === void 0 || left4 === void 0) return void 0;
64580
+ return { top: top4, right: right4, bottom: bottom4, left: left4 };
64581
+ }
64249
64582
  function parseDiagramStylesheet(options) {
64250
64583
  const stylesheet = options?.stylesheet;
64251
64584
  if (!Array.isArray(stylesheet)) {
@@ -64269,7 +64602,7 @@ ${serialized}`;
64269
64602
  return rules;
64270
64603
  }
64271
64604
  function parseStyleSelector(selector) {
64272
- const match = /^\s*(node|compartment|port|edge)(?:\.([A-Za-z0-9_-]+))?(?:\s*\[(.+)\]\s*)?$/i.exec(selector);
64605
+ const match = /^\s*(diagram|node|compartment|port|edge)(?:\.([A-Za-z0-9_-]+))?(?:\s*\[(.+)\]\s*)?$/i.exec(selector);
64273
64606
  if (!match) return void 0;
64274
64607
  return {
64275
64608
  elementKind: match[1].toLowerCase(),
@@ -64282,10 +64615,28 @@ ${serialized}`;
64282
64615
  "shape",
64283
64616
  "width",
64284
64617
  "height",
64618
+ "margin",
64619
+ "padding",
64620
+ "router",
64621
+ "connector"
64622
+ ]);
64623
+ var LEGACY_BOX_SPACING_KEYS = /* @__PURE__ */ new Set([
64624
+ "marginTop",
64625
+ "marginBottom",
64626
+ "marginLeft",
64627
+ "marginRight",
64285
64628
  "paddingTop",
64286
64629
  "paddingBottom",
64287
64630
  "paddingLeft",
64288
- "paddingRight"
64631
+ "paddingRight",
64632
+ "margin-top",
64633
+ "margin-bottom",
64634
+ "margin-left",
64635
+ "margin-right",
64636
+ "padding-top",
64637
+ "padding-bottom",
64638
+ "padding-left",
64639
+ "padding-right"
64289
64640
  ]);
64290
64641
  var ATTRS_RECORD_STYLE_KEYS = /* @__PURE__ */ new Set([
64291
64642
  "label",
@@ -64308,6 +64659,9 @@ ${serialized}`;
64308
64659
  for (const [rawKey, value] of Object.entries(raw2)) {
64309
64660
  const key = rawKey.trim();
64310
64661
  if (!key || value === void 0 || value === null) continue;
64662
+ if (LEGACY_BOX_SPACING_KEYS.has(key)) {
64663
+ continue;
64664
+ }
64311
64665
  if (OML_PASSTHROUGH_STYLE_KEYS.has(key)) {
64312
64666
  passthrough[key] = value;
64313
64667
  continue;
@@ -64567,6 +64921,17 @@ ${serialized}`;
64567
64921
  ...line2 ?? {}
64568
64922
  };
64569
64923
  }
64924
+ function resolveEdgeRouter(style2) {
64925
+ const router2 = asRecord(style2.router);
64926
+ return router2 ?? { name: "normal" };
64927
+ }
64928
+ function resolveEdgeConnector(style2) {
64929
+ const connector = asRecord(style2.connector);
64930
+ return connector ?? {
64931
+ name: "jumpover",
64932
+ args: { size: 5 }
64933
+ };
64934
+ }
64570
64935
  function resolveEdgeLabelAttrs(style2, placement, text3) {
64571
64936
  const attrs = extractStyleAttrs(style2);
64572
64937
  const base = asRecord(attrs.label);
@@ -64589,17 +64954,35 @@ ${serialized}`;
64589
64954
  fillOpacity: 0.9,
64590
64955
  stroke: "none",
64591
64956
  strokeWidth: 0,
64957
+ pointerEvents: "all",
64592
64958
  ...base ?? {},
64593
64959
  ...specific ?? {}
64594
64960
  };
64595
64961
  }
64596
- function resolvePortAttrs(style2, classes, text3) {
64962
+ function resolvePortAttrs(style2, classes, text3, side = "right", ownerStroke) {
64597
64963
  const attrs = extractStyleAttrs(style2);
64598
64964
  const body = asRecord(attrs.body);
64599
64965
  const icon = asRecord(attrs.icon);
64600
64966
  const label = asRecord(attrs.label);
64601
64967
  const imageUrl = extractImageHrefFromIcon(icon);
64602
- return {
64968
+ const labelPosition = side === "left" ? {
64969
+ textAnchor: "end",
64970
+ x: -10,
64971
+ dy: "0.9em"
64972
+ } : side === "top" ? {
64973
+ textAnchor: "middle",
64974
+ x: 0,
64975
+ dy: "-0.3em"
64976
+ } : side === "bottom" ? {
64977
+ textAnchor: "middle",
64978
+ x: 0,
64979
+ dy: "1.4em"
64980
+ } : {
64981
+ textAnchor: "start",
64982
+ x: 10,
64983
+ dy: "0.9em"
64984
+ };
64985
+ const result = {
64603
64986
  body: {
64604
64987
  width: 12,
64605
64988
  height: 12,
@@ -64607,7 +64990,7 @@ ${serialized}`;
64607
64990
  y: -6,
64608
64991
  class: ["oml-port-body", ...classes].join(" "),
64609
64992
  magnet: false,
64610
- stroke: CSS_FOCUS_BORDER,
64993
+ stroke: ownerStroke ?? CSS_EDITOR_FOREGROUND,
64611
64994
  strokeWidth: 1,
64612
64995
  fill: CSS_EDITOR_BACKGROUND,
64613
64996
  ...body ?? {}
@@ -64621,18 +65004,180 @@ ${serialized}`;
64621
65004
  display: imageUrl ? void 0 : "none",
64622
65005
  ...icon ?? {},
64623
65006
  ...imageUrl ? { href: imageUrl, xlinkHref: imageUrl, "xlink:href": imageUrl } : {}
64624
- },
64625
- label: {
65007
+ }
65008
+ };
65009
+ if (text3) {
65010
+ result.label = {
64626
65011
  text: text3,
64627
65012
  fill: CSS_EDITOR_FOREGROUND,
64628
65013
  fontFamily: 'var(--vscode-editor-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif)',
64629
65014
  fontSize: 12,
64630
- textAnchor: "start",
64631
- x: 10,
64632
- dy: "0.9em",
65015
+ ...labelPosition,
64633
65016
  ...label ?? {}
65017
+ };
65018
+ }
65019
+ return result;
65020
+ }
65021
+ function resolveBorderPortLabelPosition(side) {
65022
+ return { name: side };
65023
+ }
65024
+ function clamp3(value, min, max2) {
65025
+ return Math.max(min, Math.min(max2, value));
65026
+ }
65027
+ function computeDefaultPortPlacements(graph, nodeById, portsByOwner, boxes) {
65028
+ const placements = /* @__PURE__ */ new Map();
65029
+ const ownerCenter = (nodeId) => {
65030
+ const box = boxes.get(nodeId);
65031
+ if (!box) {
65032
+ return void 0;
65033
+ }
65034
+ return { x: box.x + box.width / 2, y: box.y + box.height / 2 };
65035
+ };
65036
+ const portIdsByOwner = /* @__PURE__ */ new Map();
65037
+ for (const [ownerId, ports] of portsByOwner.entries()) {
65038
+ portIdsByOwner.set(ownerId, new Set(ports.map((port2) => port2.id)));
65039
+ }
65040
+ const peerPortIdsByPort = /* @__PURE__ */ new Map();
65041
+ const peerCentersByPort = /* @__PURE__ */ new Map();
65042
+ for (const edge of graph.edges) {
65043
+ const source = nodeById.get(edge.sourceId);
65044
+ const target = nodeById.get(edge.targetId);
65045
+ const sourceOwnerId = source?.kind === "Port" ? source.parentId : source?.id;
65046
+ const targetOwnerId = target?.kind === "Port" ? target.parentId : target?.id;
65047
+ if (!sourceOwnerId || !targetOwnerId || sourceOwnerId === targetOwnerId) {
65048
+ continue;
65049
+ }
65050
+ const sourcePeer = ownerCenter(targetOwnerId);
65051
+ const targetPeer = ownerCenter(sourceOwnerId);
65052
+ if (source?.kind === "Port" && sourcePeer) {
65053
+ const peers = peerCentersByPort.get(source.id) ?? [];
65054
+ peers.push(sourcePeer);
65055
+ peerCentersByPort.set(source.id, peers);
65056
+ if (target?.kind === "Port") {
65057
+ const peerPortIds = peerPortIdsByPort.get(source.id) ?? [];
65058
+ peerPortIds.push(target.id);
65059
+ peerPortIdsByPort.set(source.id, peerPortIds);
65060
+ }
65061
+ }
65062
+ if (target?.kind === "Port" && targetPeer) {
65063
+ const peers = peerCentersByPort.get(target.id) ?? [];
65064
+ peers.push(targetPeer);
65065
+ peerCentersByPort.set(target.id, peers);
65066
+ if (source?.kind === "Port") {
65067
+ const peerPortIds = peerPortIdsByPort.get(target.id) ?? [];
65068
+ peerPortIds.push(source.id);
65069
+ peerPortIdsByPort.set(target.id, peerPortIds);
65070
+ }
65071
+ }
65072
+ }
65073
+ const anchorFor = (ownerId, side, ratio2) => {
65074
+ const box = boxes.get(ownerId);
65075
+ if (!box) {
65076
+ return void 0;
65077
+ }
65078
+ const clampedRatio = clamp3(ratio2, 0.05, 0.95);
65079
+ if (side === "left") {
65080
+ return { x: box.x, y: box.y + box.height * clampedRatio };
65081
+ }
65082
+ if (side === "right") {
65083
+ return { x: box.x + box.width, y: box.y + box.height * clampedRatio };
65084
+ }
65085
+ if (side === "top") {
65086
+ return { x: box.x + box.width * clampedRatio, y: box.y };
64634
65087
  }
65088
+ return { x: box.x + box.width * clampedRatio, y: box.y + box.height };
64635
65089
  };
65090
+ const currentAnchorForPort = (portId) => {
65091
+ const port2 = nodeById.get(portId);
65092
+ if (!port2?.parentId) {
65093
+ return void 0;
65094
+ }
65095
+ const placement = placements.get(portId) ?? { side: "right", ratio: 0.5 };
65096
+ return anchorFor(port2.parentId, placement.side, placement.ratio);
65097
+ };
65098
+ const peerAnchorsForPort = (portId) => {
65099
+ const peerAnchors = [];
65100
+ for (const peerPortId of peerPortIdsByPort.get(portId) ?? []) {
65101
+ const anchor2 = currentAnchorForPort(peerPortId);
65102
+ if (anchor2) {
65103
+ peerAnchors.push(anchor2);
65104
+ }
65105
+ }
65106
+ for (const peerCenter of peerCentersByPort.get(portId) ?? []) {
65107
+ peerAnchors.push(peerCenter);
65108
+ }
65109
+ return peerAnchors;
65110
+ };
65111
+ const candidateCost = (ownerId, side, portId) => {
65112
+ const owner = ownerCenter(ownerId);
65113
+ const candidate = anchorFor(ownerId, side, 0.5);
65114
+ if (!owner || !candidate) {
65115
+ return Number.POSITIVE_INFINITY;
65116
+ }
65117
+ const peerAnchors = peerAnchorsForPort(portId);
65118
+ if (peerAnchors.length === 0) {
65119
+ return side === "right" ? 0 : 1;
65120
+ }
65121
+ let cost = 0;
65122
+ for (const peer of peerAnchors) {
65123
+ cost += Math.abs(candidate.x - peer.x) + Math.abs(candidate.y - peer.y);
65124
+ }
65125
+ return cost;
65126
+ };
65127
+ const sideOrder = ["right", "left", "top", "bottom"];
65128
+ const primaryCoordinate = (side, portId) => {
65129
+ const peerAnchors = peerAnchorsForPort(portId);
65130
+ if (peerAnchors.length === 0) {
65131
+ return 0;
65132
+ }
65133
+ const values = peerAnchors.map((peer) => side === "left" || side === "right" ? peer.y : peer.x);
65134
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
65135
+ };
65136
+ for (const [ownerId, ports] of portsByOwner.entries()) {
65137
+ const count = ports.length;
65138
+ for (let index3 = 0; index3 < count; index3 += 1) {
65139
+ placements.set(ports[index3].id, {
65140
+ side: "right",
65141
+ ratio: count <= 0 ? 0.5 : (index3 + 1) / (count + 1)
65142
+ });
65143
+ }
65144
+ const ownerPortIds = portIdsByOwner.get(ownerId) ?? /* @__PURE__ */ new Set();
65145
+ for (let pass = 0; pass < 2; pass += 1) {
65146
+ for (const port2 of ports) {
65147
+ let bestSide = "right";
65148
+ let bestCost = Number.POSITIVE_INFINITY;
65149
+ for (const side of sideOrder) {
65150
+ const cost = candidateCost(ownerId, side, port2.id);
65151
+ if (cost < bestCost || cost === bestCost && sideOrder.indexOf(side) < sideOrder.indexOf(bestSide)) {
65152
+ bestCost = cost;
65153
+ bestSide = side;
65154
+ }
65155
+ }
65156
+ const existing = placements.get(port2.id) ?? { ratio: 0.5, side: bestSide };
65157
+ placements.set(port2.id, { side: bestSide, ratio: existing.ratio });
65158
+ }
65159
+ const sideGroups = /* @__PURE__ */ new Map([
65160
+ ["left", []],
65161
+ ["right", []],
65162
+ ["top", []],
65163
+ ["bottom", []]
65164
+ ]);
65165
+ for (const port2 of ports) {
65166
+ sideGroups.get(placements.get(port2.id)?.side ?? "right")?.push(port2);
65167
+ }
65168
+ for (const [side, sidePorts] of sideGroups.entries()) {
65169
+ sidePorts.sort((left4, right4) => primaryCoordinate(side, left4.id) - primaryCoordinate(side, right4.id) || left4.id.localeCompare(right4.id));
65170
+ for (let index3 = 0; index3 < sidePorts.length; index3 += 1) {
65171
+ placements.set(sidePorts[index3].id, {
65172
+ side,
65173
+ ratio: (index3 + 1) / (sidePorts.length + 1)
65174
+ });
65175
+ }
65176
+ }
65177
+ void ownerPortIds;
65178
+ }
65179
+ }
65180
+ return placements;
64636
65181
  }
64637
65182
  function toPositiveNumber(value) {
64638
65183
  if (typeof value === "number" && Number.isFinite(value) && value > 0) {
@@ -65608,9 +66153,9 @@ ${serialized}`;
65608
66153
  applyNeighborhoodFilter(graphView, filterInput.value);
65609
66154
  });
65610
66155
  toolbar.appendChild(filterInput);
65611
- const downloadButton = createDownloadButton();
66156
+ const downloadButton = createDownloadButton2();
65612
66157
  downloadButton.addEventListener("click", () => {
65613
- csvDataset.downloadCsv(toCsv(csvDataset.columns, csvDataset.rows));
66158
+ csvDataset.downloadCsv(toCsv2(csvDataset.columns, csvDataset.rows));
65614
66159
  });
65615
66160
  toolbar.appendChild(downloadButton);
65616
66161
  graphRoot.appendChild(toolbar);
@@ -65694,7 +66239,7 @@ ${serialized}`;
65694
66239
  }
65695
66240
  }
65696
66241
  }
65697
- function createDownloadButton() {
66242
+ function createDownloadButton2() {
65698
66243
  const downloadButton = document.createElement("button");
65699
66244
  downloadButton.className = "tree-download-btn";
65700
66245
  downloadButton.title = "Download CSV";
@@ -66069,7 +66614,7 @@ ${serialized}`;
66069
66614
  function toNodeId(value) {
66070
66615
  return encodeURIComponent(value);
66071
66616
  }
66072
- function toCsv(columns, rows) {
66617
+ function toCsv2(columns, rows) {
66073
66618
  const escape = (value) => {
66074
66619
  if (/[",\n\r]/.test(value)) {
66075
66620
  return `"${value.replace(/"/g, '""')}"`;
@@ -66379,7 +66924,7 @@ ${serialized}`;
66379
66924
  searchInput.className = "tree-filter";
66380
66925
  searchInput.placeholder = "Filter...";
66381
66926
  controlsRight.appendChild(searchInput);
66382
- const downloadButton = createDownloadButton2();
66927
+ const downloadButton = createDownloadButton3();
66383
66928
  controlsRight.appendChild(downloadButton);
66384
66929
  controls.appendChild(controlsLeft);
66385
66930
  controls.appendChild(controlsRight);
@@ -66657,7 +67202,7 @@ ${serialized}`;
66657
67202
  function buildMatrixKey(rowKey, columnKey) {
66658
67203
  return `${rowKey}\0${columnKey}`;
66659
67204
  }
66660
- function createDownloadButton2() {
67205
+ function createDownloadButton3() {
66661
67206
  const downloadButton = document.createElement("button");
66662
67207
  downloadButton.className = "tree-download-btn";
66663
67208
  downloadButton.title = "Download CSV";
@@ -66907,6 +67452,14 @@ ${serialized}`;
66907
67452
  ...row,
66908
67453
  cells: visibleColumnIndexes.map((columnIndex) => row.cells[columnIndex] ?? "")
66909
67454
  }));
67455
+ root2.__omlAiContext = {
67456
+ columns: visibleColumns.slice(),
67457
+ rows: rowsWithIndex.map((row) => ({
67458
+ iri: (row.cells[0] ?? "").trim(),
67459
+ cells: row.cells.slice()
67460
+ })).filter((row) => row.iri.length > 0),
67461
+ blockSource
67462
+ };
66910
67463
  const columnContexts = createColumnContexts(visibleColumns, rowsWithIndex);
66911
67464
  const treeModel = isTree ? this.createTreeModel(columns, allRowsWithIndex, visibleColumnIndexes, options) : void 0;
66912
67465
  const fullyExpandedTreeRows = isTree && treeModel ? this.flattenAllTreeRows(treeModel) : void 0;
@@ -66999,9 +67552,14 @@ ${serialized}`;
66999
67552
  return;
67000
67553
  }
67001
67554
  const filtered = this.applyFiltersAndSorting(columns, sourceRows, state);
67002
- this.requestCsvDownload(visibleColumns, filtered.map((entry) => entry.cells));
67555
+ const csvExport = this.transformCsvDownloadData(visibleColumns, filtered.map((entry) => entry.cells));
67556
+ this.requestCsvDownload(csvExport.columns, csvExport.rows);
67003
67557
  });
67004
67558
  controlsRight.appendChild(downloadButton);
67559
+ const uploadButton = this.createUploadCsvButton(visibleColumns, rowsWithIndex, isTree, blockSource);
67560
+ if (uploadButton) {
67561
+ controlsRight.appendChild(uploadButton);
67562
+ }
67005
67563
  controls.appendChild(controlsLeft);
67006
67564
  controls.appendChild(controlsRight);
67007
67565
  root2.appendChild(controls);
@@ -67628,6 +68186,12 @@ ${serialized}`;
67628
68186
  appendTokenizedValueParts(value, raw2, "table-value-part");
67629
68187
  return value;
67630
68188
  }
68189
+ createUploadCsvButton(_columns, _rows, _isTree, _blockSource) {
68190
+ return void 0;
68191
+ }
68192
+ transformCsvDownloadData(columns, rows) {
68193
+ return { columns, rows };
68194
+ }
67631
68195
  requestCsvDownload(columns, rows) {
67632
68196
  if (columns.length === 0) {
67633
68197
  return;