@synergenius/flow-weaver 0.9.5 → 0.10.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.
@@ -61049,6 +61049,13 @@ function renderSVG(graph, options = {}) {
61049
61049
  }
61050
61050
  }
61051
61051
  parts2.push(`</g>`);
61052
+ const wmX = vbX + vbWidth - 16;
61053
+ const wmY = vbY + vbHeight - 14;
61054
+ const wmBrand = themeName === "dark" ? "#8e9eff" : "#5468ff";
61055
+ parts2.push(`<g opacity="0.5">`);
61056
+ parts2.push(` <svg x="${wmX - 118}" y="${wmY - 18}" width="22" height="22" viewBox="0 0 256 256" fill="none"><path d="M80 128C134 128 122 49 176 49" stroke="${wmBrand}" stroke-width="14" stroke-linecap="round"/><path d="M80 128C134 128 122 207 176 207" stroke="${wmBrand}" stroke-width="14" stroke-linecap="round"/><rect x="28" y="102" width="52" height="52" rx="10" stroke="${wmBrand}" stroke-width="14"/><rect x="176" y="23" width="52" height="52" rx="10" stroke="${wmBrand}" stroke-width="14"/><rect x="176" y="181" width="52" height="52" rx="10" stroke="${wmBrand}" stroke-width="14"/></svg>`);
61057
+ parts2.push(` <text x="${wmX}" y="${wmY}" text-anchor="end" font-size="14" font-weight="700" fill="${wmBrand}" font-family="Montserrat, 'Segoe UI', Roboto, sans-serif">Flow Weaver</text>`);
61058
+ parts2.push(`</g>`);
61052
61059
  parts2.push(`</svg>`);
61053
61060
  return parts2.join("\n");
61054
61061
  }
@@ -61187,16 +61194,20 @@ function renderPortLabels(parts2, nodeId, inputs, outputs, theme, themeName) {
61187
61194
  }
61188
61195
 
61189
61196
  // src/diagram/html-viewer.ts
61190
- function stripSvgBackground(svg) {
61191
- let result = svg.replace(/<pattern\s+id="dot-grid"[^>]*>[\s\S]*?<\/pattern>/g, "");
61192
- result = result.replace(/<rect[^>]*fill="url\(#dot-grid\)"[^>]*\/>/g, "");
61193
- result = result.replace(/(<\/defs>\n)<rect[^>]*\/>\n/, "$1");
61194
- return result;
61197
+ function prepareSvgContent(svg) {
61198
+ const vbMatch = svg.match(/viewBox="([^"]+)"/);
61199
+ const viewBox = vbMatch ? vbMatch[1] : "0 0 800 600";
61200
+ let inner = svg.replace(/<svg[^>]*>\n?/, "").replace(/<\/svg>\s*$/, "");
61201
+ inner = inner.replace(/<pattern\s+id="dot-grid"[^>]*>[\s\S]*?<\/pattern>/g, "");
61202
+ inner = inner.replace(/<rect[^>]*fill="url\(#dot-grid\)"[^>]*\/>/g, "");
61203
+ inner = inner.replace(/(<\/defs>\n)<rect[^>]*\/>\n/, "$1");
61204
+ inner = inner.replace(/<g opacity="0\.5">[\s\S]*?Flow Weaver<\/text>\s*<\/g>/, "");
61205
+ return { inner, viewBox };
61195
61206
  }
61196
61207
  function wrapSVGInHTML(svgContent, options = {}) {
61197
61208
  const title = options.title ?? "Workflow Diagram";
61198
61209
  const theme = options.theme ?? "dark";
61199
- const svg = stripSvgBackground(svgContent);
61210
+ const { inner, viewBox } = prepareSvgContent(svgContent);
61200
61211
  const isDark = theme === "dark";
61201
61212
  const bg = isDark ? "#202139" : "#f6f7ff";
61202
61213
  const dotColor = isDark ? "rgba(142, 158, 255, 0.6)" : "rgba(84, 104, 255, 0.6)";
@@ -61206,6 +61217,7 @@ function wrapSVGInHTML(svgContent, options = {}) {
61206
61217
  const textMed = isDark ? "#babac0" : "#606060";
61207
61218
  const textLow = isDark ? "#767682" : "#999999";
61208
61219
  const surfaceHigh = isDark ? "#313143" : "#f0f0f5";
61220
+ const brandAccent = isDark ? "#8e9eff" : "#5468ff";
61209
61221
  return `<!DOCTYPE html>
61210
61222
  <html lang="en">
61211
61223
  <head>
@@ -61217,25 +61229,16 @@ function wrapSVGInHTML(svgContent, options = {}) {
61217
61229
 
61218
61230
  body {
61219
61231
  width: 100vw; height: 100vh; overflow: hidden;
61220
- background: ${bg};
61221
61232
  font-family: Montserrat, 'Segoe UI', Roboto, sans-serif;
61222
61233
  color: ${textHigh};
61223
61234
  }
61224
61235
 
61225
- #viewport {
61226
- width: 100%; height: 100%;
61227
- overflow: hidden; cursor: grab;
61236
+ #canvas {
61237
+ display: block; width: 100%; height: 100%;
61238
+ cursor: grab;
61228
61239
  touch-action: none; user-select: none;
61229
- background-image: radial-gradient(circle, ${dotColor} 7.5%, transparent 7.5%);
61230
- background-size: 20px 20px;
61231
- }
61232
- #viewport.dragging { cursor: grabbing; }
61233
-
61234
- #content {
61235
- transform-origin: 0 0;
61236
- will-change: transform;
61237
61240
  }
61238
- #content svg { display: block; width: auto; height: auto; }
61241
+ #canvas.dragging { cursor: grabbing; }
61239
61242
 
61240
61243
  /* Port labels: hidden by default, shown on node hover */
61241
61244
  .nodes > g .port-label,
@@ -61244,16 +61247,36 @@ body {
61244
61247
  opacity: 0; pointer-events: none;
61245
61248
  transition: opacity 0.15s ease-in-out;
61246
61249
  }
61247
- /* Show port labels for hovered node */
61248
- .nodes > g:hover ~ .show-port-labels .port-label,
61249
- .nodes > g:hover ~ .show-port-labels .port-type-label { opacity: 1; }
61250
61250
 
61251
61251
  /* Connection hover & dimming (attribute selector covers both main and scope connections) */
61252
61252
  path[data-source] { transition: opacity 0.2s ease, stroke-width 0.15s ease; }
61253
61253
  path[data-source]:hover { stroke-width: 4; cursor: pointer; }
61254
- body.node-active path[data-source].dimmed { opacity: 0.15; }
61254
+ body.node-active path[data-source].dimmed,
61255
+ body.port-active path[data-source].dimmed { opacity: 0.1; }
61256
+ body.port-hovered path[data-source].dimmed { opacity: 0.25; }
61257
+
61258
+ /* Port circles are interactive */
61259
+ circle[data-port-id] { cursor: pointer; }
61260
+ circle[data-port-id]:hover { stroke-width: 3; filter: brightness(1.3); }
61261
+
61262
+ /* Port-click highlighting */
61263
+ path[data-source].highlighted { opacity: 1; }
61264
+ circle[data-port-id].port-selected { filter: drop-shadow(0 0 6px currentColor); stroke-width: 4; }
61255
61265
 
61256
- /* Node hover glow */
61266
+ /* Node selection glow */
61267
+ @keyframes select-pop {
61268
+ 0% { opacity: 0; stroke-width: 0; }
61269
+ 50% { opacity: 0.6; stroke-width: 12; }
61270
+ 100% { opacity: 0.35; stroke-width: 8; }
61271
+ }
61272
+ .node-glow { fill: none; pointer-events: none; animation: select-pop 0.3s ease-out forwards; }
61273
+
61274
+ /* Port hover path highlight */
61275
+ path[data-source].port-hover { opacity: 1; }
61276
+
61277
+ /* Node hover glow + draggable cursor */
61278
+ .nodes g[data-node-id] { cursor: grab; }
61279
+ .nodes g[data-node-id]:active { cursor: grabbing; }
61257
61280
  .nodes g[data-node-id]:hover > rect:first-of-type { filter: brightness(1.08); }
61258
61281
 
61259
61282
  /* Zoom controls */
@@ -61279,7 +61302,7 @@ body.node-active path[data-source].dimmed { opacity: 0.15; }
61279
61302
 
61280
61303
  /* Info panel */
61281
61304
  #info-panel {
61282
- position: fixed; bottom: 16px; left: 16px;
61305
+ position: fixed; bottom: 52px; left: 16px;
61283
61306
  max-width: 320px; min-width: 200px;
61284
61307
  background: ${surfaceMain}; border: 1px solid ${borderSubtle};
61285
61308
  border-radius: 8px; padding: 12px 16px;
@@ -61299,6 +61322,19 @@ body.node-active path[data-source].dimmed { opacity: 0.15; }
61299
61322
  #info-panel .port-list li { padding: 1px 0; }
61300
61323
  #info-panel .port-list li::before { content: '\\2022'; margin-right: 6px; color: ${textLow}; }
61301
61324
 
61325
+ /* Branding badge */
61326
+ #branding {
61327
+ position: fixed; bottom: 16px; left: 16px;
61328
+ display: flex; align-items: center; gap: 6px;
61329
+ background: ${surfaceMain}; border: 1px solid ${borderSubtle};
61330
+ border-radius: 8px; padding: 6px 12px;
61331
+ font-size: 12px; font-weight: 600; color: ${textMed};
61332
+ text-decoration: none; z-index: 9;
61333
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
61334
+ transition: color 0.15s, border-color 0.15s;
61335
+ }
61336
+ #branding:hover { color: ${textHigh}; border-color: ${textLow}; }
61337
+
61302
61338
  /* Scroll hint */
61303
61339
  #scroll-hint {
61304
61340
  position: fixed; top: 50%; left: 50%;
@@ -61318,9 +61354,16 @@ body.node-active path[data-source].dimmed { opacity: 0.15; }
61318
61354
  </style>
61319
61355
  </head>
61320
61356
  <body>
61321
- <div id="viewport">
61322
- <div id="content">${svg}</div>
61323
- </div>
61357
+ <svg id="canvas" xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}">
61358
+ <defs>
61359
+ <pattern id="viewer-dots" width="20" height="20" patternUnits="userSpaceOnUse">
61360
+ <circle cx="10" cy="10" r="1.5" fill="${dotColor}" opacity="0.6"/>
61361
+ </pattern>
61362
+ </defs>
61363
+ <rect x="-100000" y="-100000" width="200000" height="200000" fill="${bg}"/>
61364
+ <rect x="-100000" y="-100000" width="200000" height="200000" fill="url(#viewer-dots)"/>
61365
+ <g id="diagram">${inner}</g>
61366
+ </svg>
61324
61367
  <div id="controls">
61325
61368
  <button class="ctrl-btn" id="btn-in" title="Zoom in" aria-label="Zoom in">+</button>
61326
61369
  <span id="zoom-label">100%</span>
@@ -61336,23 +61379,33 @@ body.node-active path[data-source].dimmed { opacity: 0.15; }
61336
61379
  <h3 id="info-title"></h3>
61337
61380
  <div id="info-body"></div>
61338
61381
  </div>
61382
+ <a id="branding" href="https://flowweaver.ai" target="_blank" rel="noopener">
61383
+ <svg width="16" height="16" viewBox="0 0 256 256" fill="none"><path d="M80 128C134 128 122 49 176 49" stroke="${brandAccent}" stroke-width="14" stroke-linecap="round"/><path d="M80 128C134 128 122 207 176 207" stroke="${brandAccent}" stroke-width="14" stroke-linecap="round"/><rect x="28" y="102" width="52" height="52" rx="10" stroke="${brandAccent}" stroke-width="14"/><rect x="176" y="23" width="52" height="52" rx="10" stroke="${brandAccent}" stroke-width="14"/><rect x="176" y="181" width="52" height="52" rx="10" stroke="${brandAccent}" stroke-width="14"/></svg>
61384
+ <span>Flow Weaver</span>
61385
+ </a>
61339
61386
  <div id="scroll-hint">Use <kbd id="mod-key">Ctrl</kbd> + scroll to zoom</div>
61340
61387
  <script>
61341
61388
  (function() {
61342
61389
  'use strict';
61343
61390
 
61344
- var MIN_ZOOM = 0.25, MAX_ZOOM = 3, GRID_SIZE = 20;
61345
- var viewport = document.getElementById('viewport');
61346
- var content = document.getElementById('content');
61391
+ var MIN_ZOOM = 0.25, MAX_ZOOM = 3;
61392
+ var canvas = document.getElementById('canvas');
61393
+ var content = document.getElementById('diagram');
61347
61394
  var zoomLabel = document.getElementById('zoom-label');
61348
61395
  var infoPanel = document.getElementById('info-panel');
61349
61396
  var infoTitle = document.getElementById('info-title');
61350
61397
  var infoBody = document.getElementById('info-body');
61351
61398
  var scrollHint = document.getElementById('scroll-hint');
61352
61399
 
61353
- var scale = 1, tx = 0, ty = 0;
61354
- var dragging = false, dragLast = { x: 0, y: 0 };
61400
+ // Parse the original viewBox (diagram bounding box)
61401
+ var vbParts = '${viewBox}'.split(/\\s+/).map(Number);
61402
+ var origX = vbParts[0], origY = vbParts[1], origW = vbParts[2], origH = vbParts[3];
61403
+ var vbX = origX, vbY = origY, vbW = origW, vbH = origH;
61404
+ var baseW = origW; // reference width for 100% zoom
61405
+
61406
+ var pointerDown = false, didDrag = false, dragLast = { x: 0, y: 0 };
61355
61407
  var selectedNodeId = null;
61408
+ var selectedPortId = null;
61356
61409
  var hintTimer = null;
61357
61410
 
61358
61411
  // Detect Mac for modifier key
@@ -61361,29 +61414,34 @@ body.node-active path[data-source].dimmed { opacity: 0.15; }
61361
61414
 
61362
61415
  function clamp(v, min, max) { return Math.min(max, Math.max(min, v)); }
61363
61416
 
61364
- function applyTransform() {
61365
- content.style.transform = 'translate(' + tx + 'px,' + ty + 'px) scale(' + scale + ')';
61366
- viewport.style.backgroundSize = (GRID_SIZE * scale) + 'px ' + (GRID_SIZE * scale) + 'px';
61367
- viewport.style.backgroundPosition = tx + 'px ' + ty + 'px';
61368
- zoomLabel.textContent = Math.round(scale * 100) + '%';
61417
+ function applyViewBox() {
61418
+ canvas.setAttribute('viewBox', vbX + ' ' + vbY + ' ' + vbW + ' ' + vbH);
61419
+ zoomLabel.textContent = Math.round(baseW / vbW * 100) + '%';
61369
61420
  }
61370
61421
 
61371
61422
  function fitToView() {
61372
- var svgEl = content.querySelector('svg');
61373
- if (!svgEl) { scale = 1; tx = 0; ty = 0; applyTransform(); return; }
61374
- var ww = viewport.clientWidth, wh = viewport.clientHeight;
61375
- var sw = svgEl.width.baseVal.value || svgEl.getBoundingClientRect().width;
61376
- var sh = svgEl.height.baseVal.value || svgEl.getBoundingClientRect().height;
61377
- var padding = 60;
61378
- var fitScale = Math.min((ww - padding) / sw, (wh - padding) / sh, 1);
61379
- scale = fitScale;
61380
- tx = (ww - sw * fitScale) / 2;
61381
- ty = (wh - sh * fitScale) / 2;
61382
- applyTransform();
61423
+ var pad = 60;
61424
+ var cw = canvas.clientWidth, ch = canvas.clientHeight;
61425
+ if (!cw || !ch) return;
61426
+ var dw = origW + pad * 2, dh = origH + pad * 2;
61427
+ var vpRatio = cw / ch;
61428
+ var dRatio = dw / dh;
61429
+ if (vpRatio > dRatio) {
61430
+ vbH = dh; vbW = dh * vpRatio;
61431
+ } else {
61432
+ vbW = dw; vbH = dw / vpRatio;
61433
+ }
61434
+ vbX = origX - pad - (vbW - dw) / 2;
61435
+ vbY = origY - pad - (vbH - dh) / 2;
61436
+ baseW = vbW;
61437
+ applyViewBox();
61383
61438
  }
61384
61439
 
61440
+ // Convert pixel delta to SVG coordinate delta
61441
+ function pxToSvg() { return vbW / canvas.clientWidth; }
61442
+
61385
61443
  // ---- Zoom (Ctrl/Cmd + scroll) ----
61386
- viewport.addEventListener('wheel', function(e) {
61444
+ canvas.addEventListener('wheel', function(e) {
61387
61445
  if (!e.ctrlKey && !e.metaKey) {
61388
61446
  scrollHint.classList.add('visible');
61389
61447
  clearTimeout(hintTimer);
@@ -61391,48 +61449,100 @@ body.node-active path[data-source].dimmed { opacity: 0.15; }
61391
61449
  return;
61392
61450
  }
61393
61451
  e.preventDefault();
61394
- var rect = viewport.getBoundingClientRect();
61395
- var cx = e.clientX - rect.left, cy = e.clientY - rect.top;
61396
- var oldScale = scale;
61452
+ var rect = canvas.getBoundingClientRect();
61453
+ var mx = (e.clientX - rect.left) / rect.width;
61454
+ var my = (e.clientY - rect.top) / rect.height;
61455
+ var pivotX = vbX + mx * vbW;
61456
+ var pivotY = vbY + my * vbH;
61397
61457
  var delta = clamp(e.deltaY, -10, 10);
61398
- var newScale = clamp(oldScale - delta * 0.005, MIN_ZOOM, MAX_ZOOM);
61399
- var contentX = (cx - tx) / oldScale, contentY = (cy - ty) / oldScale;
61400
- tx = cx - contentX * newScale;
61401
- ty = cy - contentY * newScale;
61402
- scale = newScale;
61403
- applyTransform();
61458
+ var factor = 1 + delta * 0.005;
61459
+ var newW = clamp(vbW * factor, baseW / MAX_ZOOM, baseW / MIN_ZOOM);
61460
+ var ratio = vbH / vbW;
61461
+ var newH = newW * ratio;
61462
+ vbX = pivotX - mx * newW;
61463
+ vbY = pivotY - my * newH;
61464
+ vbW = newW; vbH = newH;
61465
+ applyViewBox();
61404
61466
  }, { passive: false });
61405
61467
 
61406
- // ---- Pan (drag) ----
61407
- viewport.addEventListener('pointerdown', function(e) {
61468
+ // ---- Pan (drag) + Node drag ----
61469
+ var draggedNodeId = null, dragNodeStart = null, didDragNode = false;
61470
+
61471
+ canvas.addEventListener('pointerdown', function(e) {
61408
61472
  if (e.button !== 0) return;
61409
- dragging = true;
61473
+ // Check if clicking on a node body (walk up to detect data-node-id)
61474
+ var t = e.target;
61475
+ while (t && t !== canvas) {
61476
+ if (t.hasAttribute && t.hasAttribute('data-node-id')) {
61477
+ // Don't start node drag if clicking on a port circle
61478
+ if (e.target.hasAttribute && e.target.hasAttribute('data-port-id')) break;
61479
+ draggedNodeId = t.getAttribute('data-node-id');
61480
+ dragNodeStart = { x: e.clientX, y: e.clientY };
61481
+ didDragNode = false;
61482
+ canvas.setPointerCapture(e.pointerId);
61483
+ return;
61484
+ }
61485
+ t = t.parentElement;
61486
+ }
61487
+ // Canvas pan
61488
+ pointerDown = true;
61489
+ didDrag = false;
61410
61490
  dragLast = { x: e.clientX, y: e.clientY };
61411
- viewport.setPointerCapture(e.pointerId);
61412
- viewport.classList.add('dragging');
61491
+ canvas.setPointerCapture(e.pointerId);
61413
61492
  });
61414
- viewport.addEventListener('pointermove', function(e) {
61415
- if (!dragging) return;
61416
- tx += e.clientX - dragLast.x;
61417
- ty += e.clientY - dragLast.y;
61493
+
61494
+ canvas.addEventListener('pointermove', function(e) {
61495
+ var ratio = pxToSvg();
61496
+
61497
+ // Node drag
61498
+ if (draggedNodeId) {
61499
+ var dx = (e.clientX - dragNodeStart.x) * ratio;
61500
+ var dy = (e.clientY - dragNodeStart.y) * ratio;
61501
+ if (!didDragNode && (Math.abs(dx) > 3 || Math.abs(dy) > 3)) {
61502
+ didDragNode = true;
61503
+ canvas.classList.add('dragging');
61504
+ }
61505
+ if (didDragNode) {
61506
+ moveNode(draggedNodeId, dx, dy);
61507
+ }
61508
+ dragNodeStart = { x: e.clientX, y: e.clientY };
61509
+ return;
61510
+ }
61511
+
61512
+ // Canvas pan \u2014 shift the viewBox origin
61513
+ if (!pointerDown) return;
61514
+ var dxPx = e.clientX - dragLast.x, dyPx = e.clientY - dragLast.y;
61515
+ if (!didDrag && (Math.abs(dxPx) > 3 || Math.abs(dyPx) > 3)) {
61516
+ didDrag = true;
61517
+ canvas.classList.add('dragging');
61518
+ }
61519
+ if (didDrag) {
61520
+ vbX -= dxPx * ratio;
61521
+ vbY -= dyPx * ratio;
61522
+ applyViewBox();
61523
+ }
61418
61524
  dragLast = { x: e.clientX, y: e.clientY };
61419
- applyTransform();
61420
61525
  });
61421
- function endDrag() { dragging = false; viewport.classList.remove('dragging'); }
61422
- viewport.addEventListener('pointerup', endDrag);
61423
- viewport.addEventListener('pointercancel', endDrag);
61526
+
61527
+ function endDrag() {
61528
+ pointerDown = false;
61529
+ draggedNodeId = null;
61530
+ canvas.classList.remove('dragging');
61531
+ }
61532
+ canvas.addEventListener('pointerup', endDrag);
61533
+ canvas.addEventListener('pointercancel', endDrag);
61424
61534
 
61425
61535
  // ---- Zoom buttons ----
61426
61536
  function zoomBy(dir) {
61427
- var rect = viewport.getBoundingClientRect();
61428
- var cx = rect.width / 2, cy = rect.height / 2;
61429
- var oldScale = scale;
61430
- var newScale = clamp(oldScale + dir * 0.15 * oldScale, MIN_ZOOM, MAX_ZOOM);
61431
- var contentX = (cx - tx) / oldScale, contentY = (cy - ty) / oldScale;
61432
- tx = cx - contentX * newScale;
61433
- ty = cy - contentY * newScale;
61434
- scale = newScale;
61435
- applyTransform();
61537
+ var cx = vbX + vbW / 2, cy = vbY + vbH / 2;
61538
+ var factor = dir > 0 ? 0.85 : 1.18;
61539
+ var newW = clamp(vbW * factor, baseW / MAX_ZOOM, baseW / MIN_ZOOM);
61540
+ var ratio = vbH / vbW;
61541
+ var newH = newW * ratio;
61542
+ vbX = cx - newW / 2;
61543
+ vbY = cy - newH / 2;
61544
+ vbW = newW; vbH = newH;
61545
+ applyViewBox();
61436
61546
  }
61437
61547
  document.getElementById('btn-in').addEventListener('click', function() { zoomBy(1); });
61438
61548
  document.getElementById('btn-out').addEventListener('click', function() { zoomBy(-1); });
@@ -61444,7 +61554,7 @@ body.node-active path[data-source].dimmed { opacity: 0.15; }
61444
61554
  if (e.key === '+' || e.key === '=') zoomBy(1);
61445
61555
  else if (e.key === '-') zoomBy(-1);
61446
61556
  else if (e.key === '0') fitToView();
61447
- else if (e.key === 'Escape') deselectNode();
61557
+ else if (e.key === 'Escape') { deselectPort(); deselectNode(); }
61448
61558
  });
61449
61559
 
61450
61560
  // ---- Port label visibility ----
@@ -61453,7 +61563,7 @@ body.node-active path[data-source].dimmed { opacity: 0.15; }
61453
61563
  labelMap[lbl.getAttribute('data-port-label')] = lbl;
61454
61564
  });
61455
61565
 
61456
- // Build adjacency: portId \u2192 array of connected portIds
61566
+ // Build adjacency: portId -> array of connected portIds
61457
61567
  var portConnections = {};
61458
61568
  content.querySelectorAll('path[data-source]').forEach(function(p) {
61459
61569
  var src = p.getAttribute('data-source');
@@ -61465,6 +61575,125 @@ body.node-active path[data-source].dimmed { opacity: 0.15; }
61465
61575
  portConnections[tgt].push(src);
61466
61576
  });
61467
61577
 
61578
+ // ---- Connection path computation (from geometry.ts) ----
61579
+ function quadCurveControl(ax, ay, bx, by, ux, uy) {
61580
+ var dn = Math.abs(ay - by);
61581
+ return [bx + (ux * dn) / Math.abs(uy), ay];
61582
+ }
61583
+
61584
+ function computeConnectionPath(sx, sy, tx, ty) {
61585
+ var e = 0.0001;
61586
+ var ax = sx + e, ay = sy + e, hx = tx - e, hy = ty - e;
61587
+ var ramp = Math.min(20, (hx - ax) / 10);
61588
+ var bx = ax + ramp, by = ay + e, gx = hx - ramp, gy = hy - e;
61589
+ var curveSizeX = Math.min(60, Math.abs(ax - hx) / 4);
61590
+ var curveSizeY = Math.min(60, Math.abs(ay - hy) / 4);
61591
+ var curveMag = Math.sqrt(curveSizeX * curveSizeX + curveSizeY * curveSizeY);
61592
+ var bgX = gx - bx, bgY = gy - by;
61593
+ var bgLen = Math.sqrt(bgX * bgX + bgY * bgY);
61594
+ var bgUx = bgX / bgLen, bgUy = bgY / bgLen;
61595
+ var dx = bx + bgUx * curveMag, dy = by + (bgUy * curveMag) / 2;
61596
+ var ex = gx - bgUx * curveMag, ey = gy - (bgUy * curveMag) / 2;
61597
+ var deX = ex - dx, deY = ey - dy;
61598
+ var deLen = Math.sqrt(deX * deX + deY * deY);
61599
+ var deUx = deX / deLen, deUy = deY / deLen;
61600
+ var c = quadCurveControl(bx, by, dx, dy, -deUx, -deUy);
61601
+ var f = quadCurveControl(gx, gy, ex, ey, deUx, deUy);
61602
+ return 'M ' + c[0] + ',' + c[1] + ' M ' + ax + ',' + ay +
61603
+ ' L ' + bx + ',' + by + ' Q ' + c[0] + ',' + c[1] + ' ' + dx + ',' + dy +
61604
+ ' L ' + ex + ',' + ey + ' Q ' + f[0] + ',' + f[1] + ' ' + gx + ',' + gy +
61605
+ ' L ' + hx + ',' + hy;
61606
+ }
61607
+
61608
+ // ---- Port position + connection path indexes ----
61609
+ var portPositions = {};
61610
+ content.querySelectorAll('[data-port-id]').forEach(function(el) {
61611
+ var id = el.getAttribute('data-port-id');
61612
+ portPositions[id] = { cx: parseFloat(el.getAttribute('cx')), cy: parseFloat(el.getAttribute('cy')) };
61613
+ });
61614
+
61615
+ var nodeOffsets = {};
61616
+ var connIndex = [];
61617
+ content.querySelectorAll('path[data-source]').forEach(function(p) {
61618
+ var src = p.getAttribute('data-source'), tgt = p.getAttribute('data-target');
61619
+ connIndex.push({ el: p, src: src, tgt: tgt, srcNode: src.split('.')[0], tgtNode: tgt.split('.')[0] });
61620
+ });
61621
+
61622
+ // ---- Node drag: moveNode ----
61623
+ function moveNode(nodeId, dx, dy) {
61624
+ if (!nodeOffsets[nodeId]) nodeOffsets[nodeId] = { dx: 0, dy: 0 };
61625
+ var off = nodeOffsets[nodeId];
61626
+ off.dx += dx; off.dy += dy;
61627
+ var tr = 'translate(' + off.dx + ',' + off.dy + ')';
61628
+
61629
+ // Move node group
61630
+ var nodeG = content.querySelector('.nodes [data-node-id="' + CSS.escape(nodeId) + '"]');
61631
+ if (nodeG) nodeG.setAttribute('transform', tr);
61632
+
61633
+ // Move label
61634
+ var labelG = content.querySelector('[data-label-for="' + CSS.escape(nodeId) + '"]');
61635
+ if (labelG) labelG.setAttribute('transform', tr);
61636
+
61637
+ // Move port labels
61638
+ allLabelIds.forEach(function(id) {
61639
+ if (id.indexOf(nodeId + '.') === 0) {
61640
+ var el = labelMap[id];
61641
+ if (el) el.setAttribute('transform', tr);
61642
+ }
61643
+ });
61644
+
61645
+ // Update port positions
61646
+ for (var pid in portPositions) {
61647
+ if (pid.indexOf(nodeId + '.') === 0) {
61648
+ portPositions[pid].cx += dx;
61649
+ portPositions[pid].cy += dy;
61650
+ }
61651
+ }
61652
+
61653
+ // Move child nodes inside scoped parents
61654
+ if (nodeG) {
61655
+ var children = nodeG.querySelectorAll(':scope > g[data-node-id]');
61656
+ children.forEach(function(childG) {
61657
+ var childId = childG.getAttribute('data-node-id');
61658
+ if (!nodeOffsets[childId]) nodeOffsets[childId] = { dx: 0, dy: 0 };
61659
+ nodeOffsets[childId].dx += dx;
61660
+ nodeOffsets[childId].dy += dy;
61661
+ for (var pid in portPositions) {
61662
+ if (pid.indexOf(childId + '.') === 0) {
61663
+ portPositions[pid].cx += dx;
61664
+ portPositions[pid].cy += dy;
61665
+ }
61666
+ }
61667
+ var childLabel = content.querySelector('[data-label-for="' + CSS.escape(childId) + '"]');
61668
+ if (childLabel) childLabel.setAttribute('transform', 'translate(' + nodeOffsets[childId].dx + ',' + nodeOffsets[childId].dy + ')');
61669
+ allLabelIds.forEach(function(id) {
61670
+ if (id.indexOf(childId + '.') === 0) {
61671
+ var el = labelMap[id];
61672
+ if (el) el.setAttribute('transform', 'translate(' + nodeOffsets[childId].dx + ',' + nodeOffsets[childId].dy + ')');
61673
+ }
61674
+ });
61675
+ });
61676
+ }
61677
+
61678
+ // Recalculate affected connection paths
61679
+ connIndex.forEach(function(c) {
61680
+ if (c.srcNode === nodeId || c.tgtNode === nodeId) {
61681
+ var sp = portPositions[c.src], tp = portPositions[c.tgt];
61682
+ if (sp && tp) c.el.setAttribute('d', computeConnectionPath(sp.cx, sp.cy, tp.cx, tp.cy));
61683
+ }
61684
+ if (nodeG) {
61685
+ var children = nodeG.querySelectorAll(':scope > g[data-node-id]');
61686
+ children.forEach(function(childG) {
61687
+ var childId = childG.getAttribute('data-node-id');
61688
+ if (c.srcNode === childId || c.tgtNode === childId) {
61689
+ var sp = portPositions[c.src], tp = portPositions[c.tgt];
61690
+ if (sp && tp) c.el.setAttribute('d', computeConnectionPath(sp.cx, sp.cy, tp.cx, tp.cy));
61691
+ }
61692
+ });
61693
+ }
61694
+ });
61695
+ }
61696
+
61468
61697
  var allLabelIds = Object.keys(labelMap);
61469
61698
  var hoveredPort = null;
61470
61699
 
@@ -61489,7 +61718,7 @@ body.node-active path[data-source].dimmed { opacity: 0.15; }
61489
61718
  var parentNodeG = nodeG.parentElement ? nodeG.parentElement.closest('g[data-node-id]') : null;
61490
61719
  var parentId = parentNodeG ? parentNodeG.getAttribute('data-node-id') : null;
61491
61720
  nodeG.addEventListener('mouseenter', function() {
61492
- if (hoveredPort) return; // port hover takes priority
61721
+ if (hoveredPort) return;
61493
61722
  if (parentId) hideLabelsFor(parentId);
61494
61723
  showLabelsFor(nodeId);
61495
61724
  });
@@ -61508,24 +61737,93 @@ body.node-active path[data-source].dimmed { opacity: 0.15; }
61508
61737
 
61509
61738
  portEl.addEventListener('mouseenter', function() {
61510
61739
  hoveredPort = portId;
61511
- // Hide all labels for this node first, then show only the relevant ones
61512
61740
  hideLabelsFor(nodeId);
61513
61741
  peers.forEach(showLabel);
61742
+ document.body.classList.add('port-hovered');
61743
+ content.querySelectorAll('path[data-source]').forEach(function(p) {
61744
+ if (p.getAttribute('data-source') === portId || p.getAttribute('data-target') === portId) {
61745
+ p.classList.remove('dimmed');
61746
+ } else {
61747
+ p.classList.add('dimmed');
61748
+ }
61749
+ });
61514
61750
  });
61515
61751
  portEl.addEventListener('mouseleave', function() {
61516
61752
  hoveredPort = null;
61517
61753
  peers.forEach(hideLabel);
61518
- // Restore all labels for the node since we're still inside it
61519
61754
  showLabelsFor(nodeId);
61755
+ document.body.classList.remove('port-hovered');
61756
+ content.querySelectorAll('path[data-source].dimmed').forEach(function(p) {
61757
+ p.classList.remove('dimmed');
61758
+ });
61520
61759
  });
61521
61760
  });
61522
61761
 
61762
+ // ---- Node glow helpers ----
61763
+ function removeNodeGlow() {
61764
+ var glow = content.querySelector('.node-glow');
61765
+ if (glow) glow.remove();
61766
+ }
61767
+
61768
+ function addNodeGlow(nodeG) {
61769
+ removeNodeGlow();
61770
+ var rect = nodeG.querySelector('rect');
61771
+ if (!rect) return;
61772
+ var ns = 'http://www.w3.org/2000/svg';
61773
+ var glow = document.createElementNS(ns, 'rect');
61774
+ glow.setAttribute('x', rect.getAttribute('x'));
61775
+ glow.setAttribute('y', rect.getAttribute('y'));
61776
+ glow.setAttribute('width', rect.getAttribute('width'));
61777
+ glow.setAttribute('height', rect.getAttribute('height'));
61778
+ glow.setAttribute('rx', rect.getAttribute('rx') || '0');
61779
+ glow.setAttribute('stroke', rect.getAttribute('stroke') || '#5468ff');
61780
+ glow.setAttribute('class', 'node-glow');
61781
+ nodeG.insertBefore(glow, rect);
61782
+ }
61783
+
61784
+ // ---- Port selection ----
61785
+ function deselectPort() {
61786
+ if (!selectedPortId) return;
61787
+ selectedPortId = null;
61788
+ document.body.classList.remove('port-active');
61789
+ content.querySelectorAll('circle.port-selected').forEach(function(c) {
61790
+ c.classList.remove('port-selected');
61791
+ });
61792
+ content.querySelectorAll('path[data-source].dimmed, path[data-source].highlighted').forEach(function(p) {
61793
+ p.classList.remove('dimmed');
61794
+ p.classList.remove('highlighted');
61795
+ });
61796
+ }
61797
+
61798
+ function selectPort(portId) {
61799
+ if (selectedPortId === portId) { deselectPort(); return; }
61800
+ if (selectedNodeId) deselectNode();
61801
+ deselectPort();
61802
+ selectedPortId = portId;
61803
+ document.body.classList.add('port-active');
61804
+
61805
+ var portEl = content.querySelector('[data-port-id="' + CSS.escape(portId) + '"]');
61806
+ if (portEl) portEl.classList.add('port-selected');
61807
+
61808
+ content.querySelectorAll('path[data-source]').forEach(function(p) {
61809
+ if (p.getAttribute('data-source') === portId || p.getAttribute('data-target') === portId) {
61810
+ p.classList.add('highlighted');
61811
+ } else {
61812
+ p.classList.add('dimmed');
61813
+ }
61814
+ });
61815
+
61816
+ var peers = (portConnections[portId] || []).concat(portId);
61817
+ peers.forEach(showLabel);
61818
+ }
61819
+
61523
61820
  // ---- Click to inspect node ----
61524
61821
  function deselectNode() {
61525
61822
  selectedNodeId = null;
61526
61823
  document.body.classList.remove('node-active');
61527
61824
  infoPanel.classList.remove('visible');
61528
- content.querySelectorAll('.connections path.dimmed').forEach(function(p) {
61825
+ removeNodeGlow();
61826
+ content.querySelectorAll('path[data-source].dimmed').forEach(function(p) {
61529
61827
  p.classList.remove('dimmed');
61530
61828
  });
61531
61829
  }
@@ -61535,8 +61833,9 @@ body.node-active path[data-source].dimmed { opacity: 0.15; }
61535
61833
  selectedNodeId = nodeId;
61536
61834
  document.body.classList.add('node-active');
61537
61835
 
61538
- // Gather info
61539
61836
  var nodeG = content.querySelector('[data-node-id="' + CSS.escape(nodeId) + '"]');
61837
+ addNodeGlow(nodeG);
61838
+
61540
61839
  var labelG = content.querySelector('[data-label-for="' + CSS.escape(nodeId) + '"]');
61541
61840
  var labelText = labelG ? (labelG.querySelector('.node-label') || {}).textContent || nodeId : nodeId;
61542
61841
 
@@ -61553,7 +61852,6 @@ body.node-active path[data-source].dimmed { opacity: 0.15; }
61553
61852
 
61554
61853
  // Connected paths
61555
61854
  var allPaths = content.querySelectorAll('path[data-source]');
61556
- var connectedPaths = [];
61557
61855
  var connectedNodes = new Set();
61558
61856
  allPaths.forEach(function(p) {
61559
61857
  var src = p.getAttribute('data-source') || '';
@@ -61561,7 +61859,6 @@ body.node-active path[data-source].dimmed { opacity: 0.15; }
61561
61859
  var srcNode = src.split('.')[0];
61562
61860
  var tgtNode = tgt.split('.')[0];
61563
61861
  if (srcNode === nodeId || tgtNode === nodeId) {
61564
- connectedPaths.push(p);
61565
61862
  if (srcNode !== nodeId) connectedNodes.add(srcNode);
61566
61863
  if (tgtNode !== nodeId) connectedNodes.add(tgtNode);
61567
61864
  p.classList.remove('dimmed');
@@ -61596,20 +61893,25 @@ body.node-active path[data-source].dimmed { opacity: 0.15; }
61596
61893
  return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
61597
61894
  }
61598
61895
 
61599
- // Delegate click on node groups
61600
- viewport.addEventListener('click', function(e) {
61601
- if (dragging) return;
61896
+ // Delegate click: port click > node click > background
61897
+ canvas.addEventListener('click', function(e) {
61898
+ if (didDrag || didDragNode) { didDragNode = false; return; }
61602
61899
  var target = e.target;
61603
- // Walk up to find a [data-node-id] ancestor within #content
61604
- while (target && target !== viewport) {
61900
+ while (target && target !== canvas) {
61901
+ if (target.hasAttribute && target.hasAttribute('data-port-id')) {
61902
+ e.stopPropagation();
61903
+ selectPort(target.getAttribute('data-port-id'));
61904
+ return;
61905
+ }
61605
61906
  if (target.hasAttribute && target.hasAttribute('data-node-id')) {
61606
61907
  e.stopPropagation();
61908
+ deselectPort();
61607
61909
  selectNode(target.getAttribute('data-node-id'));
61608
61910
  return;
61609
61911
  }
61610
61912
  target = target.parentElement;
61611
61913
  }
61612
- // Clicked on background
61914
+ deselectPort();
61613
61915
  deselectNode();
61614
61916
  });
61615
61917
 
@@ -96210,7 +96512,7 @@ function displayInstalledPackage(pkg) {
96210
96512
  }
96211
96513
 
96212
96514
  // src/cli/index.ts
96213
- var version2 = true ? "0.9.5" : "0.0.0-dev";
96515
+ var version2 = true ? "0.10.0" : "0.0.0-dev";
96214
96516
  var program2 = new Command();
96215
96517
  program2.name("flow-weaver").description("Flow Weaver Annotations - Compile and validate workflow files").version(version2, "-v, --version", "Output the current version");
96216
96518
  program2.configureOutput({