@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.
@@ -42,6 +42,7 @@ var core$1 = require('@dnd-kit/core');
42
42
  var sortable = require('@dnd-kit/sortable');
43
43
  var utilities = require('@dnd-kit/utilities');
44
44
  var react = require('@xyflow/react');
45
+ var d3Force = require('d3-force');
45
46
  var patterns = require('@almadar/core/patterns');
46
47
  var runtime = require('@almadar/runtime');
47
48
  var registry = require('@almadar/std/registry');
@@ -34770,7 +34771,10 @@ function resolveColor3(color, el) {
34770
34771
  const resolved = getComputedStyle(el).getPropertyValue(m[1]).trim();
34771
34772
  return resolved || (m[2]?.trim() ?? "#888888");
34772
34773
  }
34773
- var GROUP_COLORS2, labelMeasureCtx, GraphCanvas;
34774
+ function truncateLabel(label) {
34775
+ return label.length > MAX_LABEL_CHARS ? label.slice(0, MAX_LABEL_CHARS - 1) + "\u2026" : label;
34776
+ }
34777
+ var GROUP_COLORS2, labelMeasureCtx, MAX_LABEL_CHARS, GraphCanvas;
34774
34778
  var init_GraphCanvas = __esm({
34775
34779
  "components/core/molecules/GraphCanvas.tsx"() {
34776
34780
  "use client";
@@ -34791,6 +34795,7 @@ var init_GraphCanvas = __esm({
34791
34795
  "var(--color-accent)"
34792
34796
  ];
34793
34797
  labelMeasureCtx = null;
34798
+ MAX_LABEL_CHARS = 22;
34794
34799
  GraphCanvas = ({
34795
34800
  title,
34796
34801
  nodes: propNodes = [],
@@ -34901,6 +34906,7 @@ var init_GraphCanvas = __esm({
34901
34906
  const w = viewW * layoutScale;
34902
34907
  const h = viewH * layoutScale;
34903
34908
  const labelFont = resolveColor3("var(--font-family)", canvas) || "system-ui";
34909
+ const clamp01 = (v) => Math.min(1, Math.max(0, v));
34904
34910
  const prevPos = /* @__PURE__ */ new Map();
34905
34911
  for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
34906
34912
  const simNodes = propNodes.map((n, idx) => {
@@ -34925,7 +34931,7 @@ var init_GraphCanvas = __esm({
34925
34931
  y = h * 0.2 + rand() * h * 0.6;
34926
34932
  }
34927
34933
  }
34928
- return { ...n, x, y, vx: 0, vy: 0, fx: 0, fy: 0 };
34934
+ return { ...n, x, y, vx: 0, vy: 0 };
34929
34935
  });
34930
34936
  nodesRef.current = simNodes;
34931
34937
  const prevIds = /* @__PURE__ */ new Set([...prevPos.keys()]);
@@ -34934,17 +34940,15 @@ var init_GraphCanvas = __esm({
34934
34940
  const overlap = prevIds.size === 0 ? 0 : kept / Math.max(prevIds.size, newIdList.length);
34935
34941
  const fullRelayout = !laidOutRef.current || overlap < 0.5;
34936
34942
  if (layout === "force") {
34937
- const maxIterations = 300;
34938
- const COOL = 0.12;
34939
- const COLLIDE_PASSES = 6;
34940
- const LABEL_GAP = 12;
34941
- const LABEL_H = 16;
34942
- const SIMILARITY_NEIGHBORS = 5;
34943
- const SIMILARITY_MIN_WEIGHT = 0.25;
34944
- const drawnPairs = /* @__PURE__ */ new Set();
34945
- for (const edge of propEdges) drawnPairs.add(edgeKeyOf(edge.source, edge.target));
34946
- const similarityNeighbors = /* @__PURE__ */ new Map();
34947
- {
34943
+ if (fullRelayout) {
34944
+ const LABEL_GAP = 12;
34945
+ const LABEL_H = 16;
34946
+ for (const n of simNodes) n.labelW = showLabels && n.label ? measureLabelWidth(truncateLabel(n.label), labelFont) : 0;
34947
+ const SIMILARITY_NEIGHBORS = 5;
34948
+ const SIMILARITY_MIN_WEIGHT = 0.25;
34949
+ const drawnPairs = /* @__PURE__ */ new Set();
34950
+ for (const edge of propEdges) drawnPairs.add(edgeKeyOf(edge.source, edge.target));
34951
+ const similarityNeighbors = /* @__PURE__ */ new Map();
34948
34952
  const bySource = /* @__PURE__ */ new Map();
34949
34953
  for (const pair of propSimilarity) {
34950
34954
  if (pair.weight < SIMILARITY_MIN_WEIGHT) continue;
@@ -34955,113 +34959,33 @@ var init_GraphCanvas = __esm({
34955
34959
  }
34956
34960
  for (const [id, arr] of bySource) {
34957
34961
  arr.sort((a, b) => b.weight - a.weight);
34958
- const keep = new Set(arr.slice(0, SIMILARITY_NEIGHBORS).map((p) => p.target));
34959
- similarityNeighbors.set(id, keep);
34962
+ similarityNeighbors.set(id, new Set(arr.slice(0, SIMILARITY_NEIGHBORS).map((p) => p.target)));
34960
34963
  }
34961
- }
34962
- const activeSimilarity = propSimilarity.filter((pair) => {
34963
- if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) return false;
34964
- const a = similarityNeighbors.get(pair.source);
34965
- const b = similarityNeighbors.get(pair.target);
34966
- return (a?.has(pair.target) ?? false) || (b?.has(pair.source) ?? false);
34967
- });
34968
- const tick = (iter, options) => {
34969
- const skipForces = options?.skipForces ?? false;
34970
- const nodes = nodesRef.current;
34971
- const centerX = w / 2;
34972
- const centerY = h / 2;
34973
- const temp = skipForces ? 0 : Math.max(COOL, 1 - iter / maxIterations);
34974
- for (const node of nodes) {
34975
- node.fx = 0;
34976
- node.fy = 0;
34977
- }
34978
- if (!skipForces) {
34979
- for (let i = 0; i < nodes.length; i++) {
34980
- for (let j = i + 1; j < nodes.length; j++) {
34981
- const dx = nodes[j].x - nodes[i].x;
34982
- const dy = nodes[j].y - nodes[i].y;
34983
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
34984
- const force = repulsion / (dist * dist) * temp;
34985
- const fx = dx / dist * force;
34986
- const fy = dy / dist * force;
34987
- nodes[i].fx -= fx;
34988
- nodes[i].fy -= fy;
34989
- nodes[j].fx += fx;
34990
- nodes[j].fy += fy;
34991
- }
34992
- }
34993
- const nodeById = new Map(nodes.map((n) => [n.id, n]));
34994
- const spring = (a, b, rest, k) => {
34995
- const dx = b.x - a.x;
34996
- const dy = b.y - a.y;
34997
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
34998
- const force = (dist - rest) * k * temp;
34999
- const fx = dx / dist * force;
35000
- const fy = dy / dist * force;
35001
- a.fx += fx;
35002
- a.fy += fy;
35003
- b.fx -= fx;
35004
- b.fy -= fy;
35005
- };
35006
- for (const edge of propEdges) {
35007
- const source = nodeById.get(edge.source);
35008
- const target = nodeById.get(edge.target);
35009
- if (!source || !target) continue;
35010
- drawnPairs.add(edgeKeyOf(edge.source, edge.target));
35011
- const w2 = Math.min(1, Math.max(0, edge.weight ?? 1));
35012
- spring(source, target, linkDistance * (0.35 + (1 - w2) * 0.3), 0.14);
35013
- }
35014
- for (const pair of activeSimilarity) {
35015
- const source = nodeById.get(pair.source);
35016
- const target = nodeById.get(pair.target);
35017
- if (!source || !target) continue;
35018
- const w2 = Math.min(1, Math.max(0, pair.weight));
35019
- spring(source, target, linkDistance * (1.35 - 0.55 * w2), 0.015);
35020
- }
35021
- const centroids = /* @__PURE__ */ new Map();
35022
- for (const node of nodes) {
35023
- const g = node.group ?? "__none__";
35024
- let c = centroids.get(g);
35025
- if (!c) {
35026
- c = { x: 0, y: 0, n: 0 };
35027
- centroids.set(g, c);
35028
- }
35029
- c.x += node.x;
35030
- c.y += node.y;
35031
- c.n += 1;
35032
- }
35033
- const multiCluster = centroids.size > 1;
35034
- for (const node of nodes) {
35035
- if (multiCluster) {
35036
- const c = centroids.get(node.group ?? "__none__");
35037
- if (c && c.n > 1) {
35038
- node.fx += (c.x / c.n - node.x) * 0.06 * temp;
35039
- node.fy += (c.y / c.n - node.y) * 0.06 * temp;
35040
- } else {
35041
- node.fx += (centerX - node.x) * 0.01 * temp;
35042
- node.fy += (centerY - node.y) * 0.01 * temp;
35043
- }
35044
- } else {
35045
- node.fx += (centerX - node.x) * 8e-3 * temp;
35046
- node.fy += (centerY - node.y) * 8e-3 * temp;
35047
- }
35048
- }
35049
- const damping = 0.9;
35050
- for (const node of nodes) {
35051
- node.vx = (node.vx + node.fx) * damping;
35052
- node.vy = (node.vy + node.fy) * damping;
35053
- node.x += node.vx;
35054
- node.y += node.vy;
35055
- }
35056
- }
35057
- for (const node of nodes) {
35058
- node.x = Math.max(30, Math.min(w - 30, node.x));
35059
- node.y = Math.max(30, Math.min(h - 30, node.y));
34964
+ const activeSimilarity = propSimilarity.filter((pair) => {
34965
+ if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) return false;
34966
+ const a = similarityNeighbors.get(pair.source);
34967
+ const b = similarityNeighbors.get(pair.target);
34968
+ return (a?.has(pair.target) ?? false) || (b?.has(pair.source) ?? false);
34969
+ });
34970
+ const groupTarget = /* @__PURE__ */ new Map();
34971
+ const multiCluster = groups.length > 1;
34972
+ if (multiCluster) {
34973
+ const ring = Math.min(w, h) * 0.34;
34974
+ groups.forEach((g, i) => {
34975
+ const a = i / groups.length * 2 * Math.PI;
34976
+ groupTarget.set(g, { x: w / 2 + ring * Math.cos(a), y: h / 2 + ring * Math.sin(a) });
34977
+ });
35060
34978
  }
34979
+ const present = new Set(simNodes.map((n) => n.id));
34980
+ 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) }));
34981
+ 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) }));
34982
+ const collideRadius = (n) => Math.max(n.size || 8, (n.labelW ?? 0) / 2) + nodeSpacing / 2;
34983
+ 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();
34984
+ for (let i = 0; i < 300; i++) simulation.tick();
35061
34985
  const pad = nodeSpacing / 2;
35062
34986
  const rectsOf = (n) => {
35063
34987
  const r = n.size || 8;
35064
- const lw = showLabels ? n.labelW ?? (n.labelW = n.label ? measureLabelWidth(n.label, labelFont) : 0) : 0;
34988
+ const lw = showLabels ? n.labelW ?? 0 : 0;
35065
34989
  return {
35066
34990
  circle: [n.x - r - pad, n.y - r - pad, n.x + r + pad, n.y + r + pad],
35067
34991
  label: [n.x - lw / 2 - pad, n.y + r + LABEL_GAP - 8, n.x + lw / 2 + pad, n.y + r + LABEL_GAP + LABEL_H]
@@ -35073,11 +34997,12 @@ var init_GraphCanvas = __esm({
35073
34997
  if (ox <= 0 || oy <= 0) return null;
35074
34998
  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 };
35075
34999
  };
35076
- for (let pass = 0; pass < COLLIDE_PASSES; pass++) {
35077
- for (let i = 0; i < nodes.length; i++) {
35078
- for (let j = i + 1; j < nodes.length; j++) {
35079
- const a = nodes[i];
35080
- const b = nodes[j];
35000
+ for (let pass = 0; pass < 24; pass++) {
35001
+ let moved = false;
35002
+ for (let i = 0; i < simNodes.length; i++) {
35003
+ for (let j = i + 1; j < simNodes.length; j++) {
35004
+ const a = simNodes[i];
35005
+ const b = simNodes[j];
35081
35006
  const ra = rectsOf(a);
35082
35007
  const rb = rectsOf(b);
35083
35008
  let best = null;
@@ -35086,31 +35011,25 @@ var init_GraphCanvas = __esm({
35086
35011
  if (s && (!best || s.depth < best.depth)) best = s;
35087
35012
  }
35088
35013
  if (best) {
35014
+ moved = true;
35089
35015
  const push = best.depth / 2;
35090
35016
  if (best.axis === "x") {
35091
35017
  a.x = Math.max(30, Math.min(w - 30, a.x - push * best.sign));
35092
35018
  b.x = Math.max(30, Math.min(w - 30, b.x + push * best.sign));
35093
- a.vx = 0;
35094
- b.vx = 0;
35095
35019
  } else {
35096
35020
  a.y = Math.max(30, Math.min(h - 30, a.y - push * best.sign));
35097
35021
  b.y = Math.max(30, Math.min(h - 30, b.y + push * best.sign));
35098
- a.vy = 0;
35099
- b.vy = 0;
35100
35022
  }
35101
35023
  }
35102
35024
  }
35103
35025
  }
35026
+ if (!moved) break;
35104
35027
  }
35105
- };
35106
- if (fullRelayout) {
35107
- for (let i = 0; i < maxIterations; i++) tick(i);
35108
- for (let i = 0; i < 4; i++) tick(maxIterations, { skipForces: true });
35109
35028
  const fitPad = 40;
35110
35029
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
35111
35030
  for (const node of nodesRef.current) {
35112
35031
  const r = node.size || 8;
35113
- const lw = showLabels ? node.labelW ?? (node.labelW = node.label ? measureLabelWidth(node.label, labelFont) : 0) : 0;
35032
+ const lw = showLabels ? node.labelW ?? 0 : 0;
35114
35033
  minX = Math.min(minX, node.x - r, node.x - lw / 2);
35115
35034
  minY = Math.min(minY, node.y - r);
35116
35035
  maxX = Math.max(maxX, node.x + r, node.x + lw / 2);
@@ -35231,16 +35150,17 @@ var init_GraphCanvas = __esm({
35231
35150
  }
35232
35151
  ctx.stroke();
35233
35152
  if (showLabels && node.label) {
35234
- ctx.font = `${isSelected || isHovered ? "600" : "500"} 12px ${fontFamily}`;
35153
+ const displayLabel = truncateLabel(node.label);
35154
+ ctx.font = `${isSelected || isHovered ? "700" : "600"} 12px ${fontFamily}`;
35235
35155
  ctx.textAlign = "center";
35236
35156
  ctx.textBaseline = "middle";
35237
35157
  const ly = node.y + radius + 14;
35238
- ctx.lineWidth = 3;
35158
+ ctx.lineWidth = 4;
35239
35159
  ctx.lineJoin = "round";
35240
35160
  ctx.strokeStyle = bgColor;
35241
- ctx.strokeText(node.label, node.x, ly);
35242
- ctx.fillStyle = isSelected || isHovered ? fgColor : mutedColor;
35243
- ctx.fillText(node.label, node.x, ly);
35161
+ ctx.strokeText(displayLabel, node.x, ly);
35162
+ ctx.fillStyle = fgColor;
35163
+ ctx.fillText(displayLabel, node.x, ly);
35244
35164
  }
35245
35165
  if (node.badge && node.badge > 1) {
35246
35166
  const bx = node.x + radius * 0.7;
@@ -35388,6 +35308,20 @@ var init_GraphCanvas = __esm({
35388
35308
  },
35389
35309
  [toCoords, nodeAt, onNodeDoubleClick]
35390
35310
  );
35311
+ const hoveredObj = hoveredNode ? nodesRef.current.find((n) => n.id === hoveredNode) : void 0;
35312
+ const canvasEl = canvasRef.current;
35313
+ const showLabelTooltip = Boolean(
35314
+ hoveredObj?.label && hoveredObj.label.length > MAX_LABEL_CHARS && hoveredObj.x != null && canvasEl
35315
+ );
35316
+ const labelTooltipStyle = (() => {
35317
+ if (!showLabelTooltip || !canvasEl || !hoveredObj || hoveredObj.x == null || hoveredObj.y == null) return void 0;
35318
+ const rect = canvasEl.getBoundingClientRect();
35319
+ return {
35320
+ left: rect.left + offset.x + hoveredObj.x * zoom,
35321
+ top: rect.top + offset.y + hoveredObj.y * zoom - 10,
35322
+ transform: "translate(-50%, -100%)"
35323
+ };
35324
+ })();
35391
35325
  if (isLoading) {
35392
35326
  return /* @__PURE__ */ jsxRuntime.jsx(LoadingState, { message: t("common.loading"), className });
35393
35327
  }
@@ -35441,23 +35375,43 @@ var init_GraphCanvas = __esm({
35441
35375
  ]
35442
35376
  }
35443
35377
  ),
35444
- /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full bg-background", children: /* @__PURE__ */ jsxRuntime.jsx(
35445
- "canvas",
35446
- {
35447
- ref: canvasRef,
35448
- width: Math.round(logicalW * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
35449
- height: Math.round(height * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
35450
- className: "w-full cursor-grab active:cursor-grabbing touch-none",
35451
- style: { height },
35452
- onPointerDown: gestureHandlers.onPointerDown,
35453
- onPointerMove: gestureHandlers.onPointerMove,
35454
- onPointerUp: gestureHandlers.onPointerUp,
35455
- onPointerCancel: gestureHandlers.onPointerCancel,
35456
- onPointerLeave: handlePointerLeave,
35457
- onWheel: gestureHandlers.onWheel,
35458
- onDoubleClick: handleDoubleClick
35459
- }
35460
- ) }),
35378
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "w-full bg-background", children: [
35379
+ /* @__PURE__ */ jsxRuntime.jsx(
35380
+ "canvas",
35381
+ {
35382
+ ref: canvasRef,
35383
+ width: Math.round(logicalW * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
35384
+ height: Math.round(height * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
35385
+ className: "w-full cursor-grab active:cursor-grabbing touch-none",
35386
+ style: { height },
35387
+ onPointerDown: gestureHandlers.onPointerDown,
35388
+ onPointerMove: gestureHandlers.onPointerMove,
35389
+ onPointerUp: gestureHandlers.onPointerUp,
35390
+ onPointerCancel: gestureHandlers.onPointerCancel,
35391
+ onPointerLeave: handlePointerLeave,
35392
+ onWheel: gestureHandlers.onWheel,
35393
+ onDoubleClick: handleDoubleClick
35394
+ }
35395
+ ),
35396
+ typeof window !== "undefined" && showLabelTooltip && labelTooltipStyle && reactDom.createPortal(
35397
+ /* @__PURE__ */ jsxRuntime.jsx(
35398
+ "div",
35399
+ {
35400
+ className: cn(
35401
+ "fixed z-50 px-3 py-2 max-w-xs",
35402
+ "bg-primary text-primary-foreground",
35403
+ "shadow-elevation-popover rounded-sm",
35404
+ "text-sm pointer-events-none",
35405
+ "break-words whitespace-normal"
35406
+ ),
35407
+ style: labelTooltipStyle,
35408
+ role: "tooltip",
35409
+ children: hoveredObj?.label
35410
+ }
35411
+ ),
35412
+ document.body
35413
+ )
35414
+ ] }),
35461
35415
  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: [
35462
35416
  /* @__PURE__ */ jsxRuntime.jsx(
35463
35417
  Box,