@almadar/ui 5.120.0 → 5.121.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,6 +40,7 @@ var core = require('@almadar/core');
40
40
  var core$1 = require('@dnd-kit/core');
41
41
  var sortable = require('@dnd-kit/sortable');
42
42
  var utilities = require('@dnd-kit/utilities');
43
+ var d3Force = require('d3-force');
43
44
  var patterns = require('@almadar/core/patterns');
44
45
  var OrbitalServerRuntime = require('@almadar/runtime/OrbitalServerRuntime');
45
46
  var registry = require('@almadar/std/registry');
@@ -37275,7 +37276,10 @@ function resolveColor3(color, el) {
37275
37276
  const resolved = getComputedStyle(el).getPropertyValue(m[1]).trim();
37276
37277
  return resolved || (m[2]?.trim() ?? "#888888");
37277
37278
  }
37278
- var GROUP_COLORS2, labelMeasureCtx, GraphCanvas;
37279
+ function truncateLabel(label) {
37280
+ return label.length > MAX_LABEL_CHARS ? label.slice(0, MAX_LABEL_CHARS - 1) + "\u2026" : label;
37281
+ }
37282
+ var GROUP_COLORS2, labelMeasureCtx, MAX_LABEL_CHARS, GraphCanvas;
37279
37283
  var init_GraphCanvas = __esm({
37280
37284
  "components/core/molecules/GraphCanvas.tsx"() {
37281
37285
  "use client";
@@ -37296,6 +37300,7 @@ var init_GraphCanvas = __esm({
37296
37300
  "var(--color-accent)"
37297
37301
  ];
37298
37302
  labelMeasureCtx = null;
37303
+ MAX_LABEL_CHARS = 22;
37299
37304
  GraphCanvas = ({
37300
37305
  title,
37301
37306
  nodes: propNodes = [],
@@ -37406,6 +37411,7 @@ var init_GraphCanvas = __esm({
37406
37411
  const w = viewW * layoutScale;
37407
37412
  const h = viewH * layoutScale;
37408
37413
  const labelFont = resolveColor3("var(--font-family)", canvas) || "system-ui";
37414
+ const clamp01 = (v) => Math.min(1, Math.max(0, v));
37409
37415
  const prevPos = /* @__PURE__ */ new Map();
37410
37416
  for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
37411
37417
  const simNodes = propNodes.map((n, idx) => {
@@ -37430,7 +37436,7 @@ var init_GraphCanvas = __esm({
37430
37436
  y = h * 0.2 + rand() * h * 0.6;
37431
37437
  }
37432
37438
  }
37433
- return { ...n, x, y, vx: 0, vy: 0, fx: 0, fy: 0 };
37439
+ return { ...n, x, y, vx: 0, vy: 0 };
37434
37440
  });
37435
37441
  nodesRef.current = simNodes;
37436
37442
  const prevIds = /* @__PURE__ */ new Set([...prevPos.keys()]);
@@ -37439,17 +37445,15 @@ var init_GraphCanvas = __esm({
37439
37445
  const overlap = prevIds.size === 0 ? 0 : kept / Math.max(prevIds.size, newIdList.length);
37440
37446
  const fullRelayout = !laidOutRef.current || overlap < 0.5;
37441
37447
  if (layout === "force") {
37442
- const maxIterations = 300;
37443
- const COOL = 0.12;
37444
- const COLLIDE_PASSES = 6;
37445
- const LABEL_GAP = 12;
37446
- const LABEL_H = 16;
37447
- const SIMILARITY_NEIGHBORS = 5;
37448
- const SIMILARITY_MIN_WEIGHT = 0.25;
37449
- const drawnPairs = /* @__PURE__ */ new Set();
37450
- for (const edge of propEdges) drawnPairs.add(edgeKeyOf(edge.source, edge.target));
37451
- const similarityNeighbors = /* @__PURE__ */ new Map();
37452
- {
37448
+ if (fullRelayout) {
37449
+ const LABEL_GAP = 12;
37450
+ const LABEL_H = 16;
37451
+ for (const n of simNodes) n.labelW = showLabels && n.label ? measureLabelWidth(truncateLabel(n.label), labelFont) : 0;
37452
+ const SIMILARITY_NEIGHBORS = 5;
37453
+ const SIMILARITY_MIN_WEIGHT = 0.25;
37454
+ const drawnPairs = /* @__PURE__ */ new Set();
37455
+ for (const edge of propEdges) drawnPairs.add(edgeKeyOf(edge.source, edge.target));
37456
+ const similarityNeighbors = /* @__PURE__ */ new Map();
37453
37457
  const bySource = /* @__PURE__ */ new Map();
37454
37458
  for (const pair of propSimilarity) {
37455
37459
  if (pair.weight < SIMILARITY_MIN_WEIGHT) continue;
@@ -37460,113 +37464,33 @@ var init_GraphCanvas = __esm({
37460
37464
  }
37461
37465
  for (const [id, arr] of bySource) {
37462
37466
  arr.sort((a, b) => b.weight - a.weight);
37463
- const keep = new Set(arr.slice(0, SIMILARITY_NEIGHBORS).map((p) => p.target));
37464
- similarityNeighbors.set(id, keep);
37467
+ similarityNeighbors.set(id, new Set(arr.slice(0, SIMILARITY_NEIGHBORS).map((p) => p.target)));
37465
37468
  }
37466
- }
37467
- const activeSimilarity = propSimilarity.filter((pair) => {
37468
- if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) return false;
37469
- const a = similarityNeighbors.get(pair.source);
37470
- const b = similarityNeighbors.get(pair.target);
37471
- return (a?.has(pair.target) ?? false) || (b?.has(pair.source) ?? false);
37472
- });
37473
- const tick = (iter, options) => {
37474
- const skipForces = options?.skipForces ?? false;
37475
- const nodes = nodesRef.current;
37476
- const centerX = w / 2;
37477
- const centerY = h / 2;
37478
- const temp = skipForces ? 0 : Math.max(COOL, 1 - iter / maxIterations);
37479
- for (const node of nodes) {
37480
- node.fx = 0;
37481
- node.fy = 0;
37482
- }
37483
- if (!skipForces) {
37484
- for (let i = 0; i < nodes.length; i++) {
37485
- for (let j = i + 1; j < nodes.length; j++) {
37486
- const dx = nodes[j].x - nodes[i].x;
37487
- const dy = nodes[j].y - nodes[i].y;
37488
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37489
- const force = repulsion / (dist * dist) * temp;
37490
- const fx = dx / dist * force;
37491
- const fy = dy / dist * force;
37492
- nodes[i].fx -= fx;
37493
- nodes[i].fy -= fy;
37494
- nodes[j].fx += fx;
37495
- nodes[j].fy += fy;
37496
- }
37497
- }
37498
- const nodeById = new Map(nodes.map((n) => [n.id, n]));
37499
- const spring = (a, b, rest, k) => {
37500
- const dx = b.x - a.x;
37501
- const dy = b.y - a.y;
37502
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37503
- const force = (dist - rest) * k * temp;
37504
- const fx = dx / dist * force;
37505
- const fy = dy / dist * force;
37506
- a.fx += fx;
37507
- a.fy += fy;
37508
- b.fx -= fx;
37509
- b.fy -= fy;
37510
- };
37511
- for (const edge of propEdges) {
37512
- const source = nodeById.get(edge.source);
37513
- const target = nodeById.get(edge.target);
37514
- if (!source || !target) continue;
37515
- drawnPairs.add(edgeKeyOf(edge.source, edge.target));
37516
- const w2 = Math.min(1, Math.max(0, edge.weight ?? 1));
37517
- spring(source, target, linkDistance * (0.35 + (1 - w2) * 0.3), 0.14);
37518
- }
37519
- for (const pair of activeSimilarity) {
37520
- const source = nodeById.get(pair.source);
37521
- const target = nodeById.get(pair.target);
37522
- if (!source || !target) continue;
37523
- const w2 = Math.min(1, Math.max(0, pair.weight));
37524
- spring(source, target, linkDistance * (1.35 - 0.55 * w2), 0.015);
37525
- }
37526
- const centroids = /* @__PURE__ */ new Map();
37527
- for (const node of nodes) {
37528
- const g = node.group ?? "__none__";
37529
- let c = centroids.get(g);
37530
- if (!c) {
37531
- c = { x: 0, y: 0, n: 0 };
37532
- centroids.set(g, c);
37533
- }
37534
- c.x += node.x;
37535
- c.y += node.y;
37536
- c.n += 1;
37537
- }
37538
- const multiCluster = centroids.size > 1;
37539
- for (const node of nodes) {
37540
- if (multiCluster) {
37541
- const c = centroids.get(node.group ?? "__none__");
37542
- if (c && c.n > 1) {
37543
- node.fx += (c.x / c.n - node.x) * 0.06 * temp;
37544
- node.fy += (c.y / c.n - node.y) * 0.06 * temp;
37545
- } else {
37546
- node.fx += (centerX - node.x) * 0.01 * temp;
37547
- node.fy += (centerY - node.y) * 0.01 * temp;
37548
- }
37549
- } else {
37550
- node.fx += (centerX - node.x) * 8e-3 * temp;
37551
- node.fy += (centerY - node.y) * 8e-3 * temp;
37552
- }
37553
- }
37554
- const damping = 0.9;
37555
- for (const node of nodes) {
37556
- node.vx = (node.vx + node.fx) * damping;
37557
- node.vy = (node.vy + node.fy) * damping;
37558
- node.x += node.vx;
37559
- node.y += node.vy;
37560
- }
37561
- }
37562
- for (const node of nodes) {
37563
- node.x = Math.max(30, Math.min(w - 30, node.x));
37564
- node.y = Math.max(30, Math.min(h - 30, node.y));
37469
+ const activeSimilarity = propSimilarity.filter((pair) => {
37470
+ if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) return false;
37471
+ const a = similarityNeighbors.get(pair.source);
37472
+ const b = similarityNeighbors.get(pair.target);
37473
+ return (a?.has(pair.target) ?? false) || (b?.has(pair.source) ?? false);
37474
+ });
37475
+ const groupTarget = /* @__PURE__ */ new Map();
37476
+ const multiCluster = groups.length > 1;
37477
+ if (multiCluster) {
37478
+ const ring = Math.min(w, h) * 0.34;
37479
+ groups.forEach((g, i) => {
37480
+ const a = i / groups.length * 2 * Math.PI;
37481
+ groupTarget.set(g, { x: w / 2 + ring * Math.cos(a), y: h / 2 + ring * Math.sin(a) });
37482
+ });
37565
37483
  }
37484
+ const present = new Set(simNodes.map((n) => n.id));
37485
+ const edgeLinks = propEdges.filter((e) => present.has(e.source) && present.has(e.target)).map((e) => ({ source: e.source, target: e.target, weight: clamp01(e.weight ?? 1) }));
37486
+ const simLinks = activeSimilarity.filter((p) => present.has(p.source) && present.has(p.target)).map((p) => ({ source: p.source, target: p.target, weight: clamp01(p.weight) }));
37487
+ const collideRadius = (n) => Math.max(n.size || 8, (n.labelW ?? 0) / 2) + nodeSpacing / 2;
37488
+ const simulation = d3Force.forceSimulation(simNodes).force("edge", d3Force.forceLink(edgeLinks).id((d) => d.id).distance((l) => linkDistance * (0.35 + (1 - l.weight) * 0.3)).strength(0.9)).force("similarity", d3Force.forceLink(simLinks).id((d) => d.id).distance((l) => linkDistance * (1.35 - 0.55 * l.weight)).strength(0.03)).force("charge", d3Force.forceManyBody().strength(-repulsion * 0.5)).force("collide", d3Force.forceCollide().radius(collideRadius).strength(0.9)).force("x", d3Force.forceX((n) => groupTarget.get(n.group ?? "")?.x ?? w / 2).strength(multiCluster ? 0.05 : 0.02)).force("y", d3Force.forceY((n) => groupTarget.get(n.group ?? "")?.y ?? h / 2).strength(multiCluster ? 0.05 : 0.02)).stop();
37489
+ for (let i = 0; i < 300; i++) simulation.tick();
37566
37490
  const pad = nodeSpacing / 2;
37567
37491
  const rectsOf = (n) => {
37568
37492
  const r2 = n.size || 8;
37569
- const lw = showLabels ? n.labelW ?? (n.labelW = n.label ? measureLabelWidth(n.label, labelFont) : 0) : 0;
37493
+ const lw = showLabels ? n.labelW ?? 0 : 0;
37570
37494
  return {
37571
37495
  circle: [n.x - r2 - pad, n.y - r2 - pad, n.x + r2 + pad, n.y + r2 + pad],
37572
37496
  label: [n.x - lw / 2 - pad, n.y + r2 + LABEL_GAP - 8, n.x + lw / 2 + pad, n.y + r2 + LABEL_GAP + LABEL_H]
@@ -37578,11 +37502,12 @@ var init_GraphCanvas = __esm({
37578
37502
  if (ox <= 0 || oy <= 0) return null;
37579
37503
  return ox <= oy ? { axis: "x", depth: ox, sign: r1[0] + r1[2] < r2[0] + r2[2] ? 1 : -1 } : { axis: "y", depth: oy, sign: r1[1] + r1[3] < r2[1] + r2[3] ? 1 : -1 };
37580
37504
  };
37581
- for (let pass = 0; pass < COLLIDE_PASSES; pass++) {
37582
- for (let i = 0; i < nodes.length; i++) {
37583
- for (let j = i + 1; j < nodes.length; j++) {
37584
- const a = nodes[i];
37585
- const b = nodes[j];
37505
+ for (let pass = 0; pass < 24; pass++) {
37506
+ let moved = false;
37507
+ for (let i = 0; i < simNodes.length; i++) {
37508
+ for (let j = i + 1; j < simNodes.length; j++) {
37509
+ const a = simNodes[i];
37510
+ const b = simNodes[j];
37586
37511
  const ra = rectsOf(a);
37587
37512
  const rb = rectsOf(b);
37588
37513
  let best = null;
@@ -37591,31 +37516,25 @@ var init_GraphCanvas = __esm({
37591
37516
  if (s && (!best || s.depth < best.depth)) best = s;
37592
37517
  }
37593
37518
  if (best) {
37519
+ moved = true;
37594
37520
  const push = best.depth / 2;
37595
37521
  if (best.axis === "x") {
37596
37522
  a.x = Math.max(30, Math.min(w - 30, a.x - push * best.sign));
37597
37523
  b.x = Math.max(30, Math.min(w - 30, b.x + push * best.sign));
37598
- a.vx = 0;
37599
- b.vx = 0;
37600
37524
  } else {
37601
37525
  a.y = Math.max(30, Math.min(h - 30, a.y - push * best.sign));
37602
37526
  b.y = Math.max(30, Math.min(h - 30, b.y + push * best.sign));
37603
- a.vy = 0;
37604
- b.vy = 0;
37605
37527
  }
37606
37528
  }
37607
37529
  }
37608
37530
  }
37531
+ if (!moved) break;
37609
37532
  }
37610
- };
37611
- if (fullRelayout) {
37612
- for (let i = 0; i < maxIterations; i++) tick(i);
37613
- for (let i = 0; i < 4; i++) tick(maxIterations, { skipForces: true });
37614
37533
  const fitPad = 40;
37615
37534
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
37616
37535
  for (const node of nodesRef.current) {
37617
37536
  const r2 = node.size || 8;
37618
- const lw = showLabels ? node.labelW ?? (node.labelW = node.label ? measureLabelWidth(node.label, labelFont) : 0) : 0;
37537
+ const lw = showLabels ? node.labelW ?? 0 : 0;
37619
37538
  minX = Math.min(minX, node.x - r2, node.x - lw / 2);
37620
37539
  minY = Math.min(minY, node.y - r2);
37621
37540
  maxX = Math.max(maxX, node.x + r2, node.x + lw / 2);
@@ -37736,16 +37655,17 @@ var init_GraphCanvas = __esm({
37736
37655
  }
37737
37656
  ctx.stroke();
37738
37657
  if (showLabels && node.label) {
37739
- ctx.font = `${isSelected || isHovered ? "600" : "500"} 12px ${fontFamily}`;
37658
+ const displayLabel = truncateLabel(node.label);
37659
+ ctx.font = `${isSelected || isHovered ? "700" : "600"} 12px ${fontFamily}`;
37740
37660
  ctx.textAlign = "center";
37741
37661
  ctx.textBaseline = "middle";
37742
37662
  const ly = node.y + radius + 14;
37743
- ctx.lineWidth = 3;
37663
+ ctx.lineWidth = 4;
37744
37664
  ctx.lineJoin = "round";
37745
37665
  ctx.strokeStyle = bgColor;
37746
- ctx.strokeText(node.label, node.x, ly);
37747
- ctx.fillStyle = isSelected || isHovered ? fgColor : mutedColor;
37748
- ctx.fillText(node.label, node.x, ly);
37666
+ ctx.strokeText(displayLabel, node.x, ly);
37667
+ ctx.fillStyle = fgColor;
37668
+ ctx.fillText(displayLabel, node.x, ly);
37749
37669
  }
37750
37670
  if (node.badge && node.badge > 1) {
37751
37671
  const bx = node.x + radius * 0.7;
@@ -37893,6 +37813,20 @@ var init_GraphCanvas = __esm({
37893
37813
  },
37894
37814
  [toCoords, nodeAt, onNodeDoubleClick]
37895
37815
  );
37816
+ const hoveredObj = hoveredNode ? nodesRef.current.find((n) => n.id === hoveredNode) : void 0;
37817
+ const canvasEl = canvasRef.current;
37818
+ const showLabelTooltip = Boolean(
37819
+ hoveredObj?.label && hoveredObj.label.length > MAX_LABEL_CHARS && hoveredObj.x != null && canvasEl
37820
+ );
37821
+ const labelTooltipStyle = (() => {
37822
+ if (!showLabelTooltip || !canvasEl || !hoveredObj || hoveredObj.x == null || hoveredObj.y == null) return void 0;
37823
+ const rect = canvasEl.getBoundingClientRect();
37824
+ return {
37825
+ left: rect.left + offset.x + hoveredObj.x * zoom,
37826
+ top: rect.top + offset.y + hoveredObj.y * zoom - 10,
37827
+ transform: "translate(-50%, -100%)"
37828
+ };
37829
+ })();
37896
37830
  if (isLoading) {
37897
37831
  return /* @__PURE__ */ jsxRuntime.jsx(LoadingState, { message: t("common.loading"), className });
37898
37832
  }
@@ -37946,23 +37880,43 @@ var init_GraphCanvas = __esm({
37946
37880
  ]
37947
37881
  }
37948
37882
  ),
37949
- /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full bg-background", children: /* @__PURE__ */ jsxRuntime.jsx(
37950
- "canvas",
37951
- {
37952
- ref: canvasRef,
37953
- width: Math.round(logicalW * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
37954
- height: Math.round(height * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
37955
- className: "w-full cursor-grab active:cursor-grabbing touch-none",
37956
- style: { height },
37957
- onPointerDown: gestureHandlers.onPointerDown,
37958
- onPointerMove: gestureHandlers.onPointerMove,
37959
- onPointerUp: gestureHandlers.onPointerUp,
37960
- onPointerCancel: gestureHandlers.onPointerCancel,
37961
- onPointerLeave: handlePointerLeave,
37962
- onWheel: gestureHandlers.onWheel,
37963
- onDoubleClick: handleDoubleClick
37964
- }
37965
- ) }),
37883
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "w-full bg-background", children: [
37884
+ /* @__PURE__ */ jsxRuntime.jsx(
37885
+ "canvas",
37886
+ {
37887
+ ref: canvasRef,
37888
+ width: Math.round(logicalW * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
37889
+ height: Math.round(height * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
37890
+ className: "w-full cursor-grab active:cursor-grabbing touch-none",
37891
+ style: { height },
37892
+ onPointerDown: gestureHandlers.onPointerDown,
37893
+ onPointerMove: gestureHandlers.onPointerMove,
37894
+ onPointerUp: gestureHandlers.onPointerUp,
37895
+ onPointerCancel: gestureHandlers.onPointerCancel,
37896
+ onPointerLeave: handlePointerLeave,
37897
+ onWheel: gestureHandlers.onWheel,
37898
+ onDoubleClick: handleDoubleClick
37899
+ }
37900
+ ),
37901
+ typeof window !== "undefined" && showLabelTooltip && labelTooltipStyle && reactDom.createPortal(
37902
+ /* @__PURE__ */ jsxRuntime.jsx(
37903
+ "div",
37904
+ {
37905
+ className: cn(
37906
+ "fixed z-50 px-3 py-2 max-w-xs",
37907
+ "bg-primary text-primary-foreground",
37908
+ "shadow-elevation-popover rounded-sm",
37909
+ "text-sm pointer-events-none",
37910
+ "break-words whitespace-normal"
37911
+ ),
37912
+ style: labelTooltipStyle,
37913
+ role: "tooltip",
37914
+ children: hoveredObj?.label
37915
+ }
37916
+ ),
37917
+ document.body
37918
+ )
37919
+ ] }),
37966
37920
  groups.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "md", className: "px-4 py-2 border-t border-border", wrap: true, children: groups.map((group, idx) => /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
37967
37921
  /* @__PURE__ */ jsxRuntime.jsx(
37968
37922
  Box,