@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 reactQuery = require('@tanstack/react-query');
47
48
 
@@ -36664,7 +36665,10 @@ function resolveColor3(color, el) {
36664
36665
  const resolved = getComputedStyle(el).getPropertyValue(m[1]).trim();
36665
36666
  return resolved || (m[2]?.trim() ?? "#888888");
36666
36667
  }
36667
- var GROUP_COLORS2, labelMeasureCtx; exports.GraphCanvas = void 0;
36668
+ function truncateLabel(label) {
36669
+ return label.length > MAX_LABEL_CHARS ? label.slice(0, MAX_LABEL_CHARS - 1) + "\u2026" : label;
36670
+ }
36671
+ var GROUP_COLORS2, labelMeasureCtx, MAX_LABEL_CHARS; exports.GraphCanvas = void 0;
36668
36672
  var init_GraphCanvas = __esm({
36669
36673
  "components/core/molecules/GraphCanvas.tsx"() {
36670
36674
  "use client";
@@ -36685,6 +36689,7 @@ var init_GraphCanvas = __esm({
36685
36689
  "var(--color-accent)"
36686
36690
  ];
36687
36691
  labelMeasureCtx = null;
36692
+ MAX_LABEL_CHARS = 22;
36688
36693
  exports.GraphCanvas = ({
36689
36694
  title,
36690
36695
  nodes: propNodes = [],
@@ -36795,6 +36800,7 @@ var init_GraphCanvas = __esm({
36795
36800
  const w = viewW * layoutScale;
36796
36801
  const h = viewH * layoutScale;
36797
36802
  const labelFont = resolveColor3("var(--font-family)", canvas) || "system-ui";
36803
+ const clamp01 = (v) => Math.min(1, Math.max(0, v));
36798
36804
  const prevPos = /* @__PURE__ */ new Map();
36799
36805
  for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
36800
36806
  const simNodes = propNodes.map((n, idx) => {
@@ -36819,7 +36825,7 @@ var init_GraphCanvas = __esm({
36819
36825
  y = h * 0.2 + rand() * h * 0.6;
36820
36826
  }
36821
36827
  }
36822
- return { ...n, x, y, vx: 0, vy: 0, fx: 0, fy: 0 };
36828
+ return { ...n, x, y, vx: 0, vy: 0 };
36823
36829
  });
36824
36830
  nodesRef.current = simNodes;
36825
36831
  const prevIds = /* @__PURE__ */ new Set([...prevPos.keys()]);
@@ -36828,17 +36834,15 @@ var init_GraphCanvas = __esm({
36828
36834
  const overlap = prevIds.size === 0 ? 0 : kept / Math.max(prevIds.size, newIdList.length);
36829
36835
  const fullRelayout = !laidOutRef.current || overlap < 0.5;
36830
36836
  if (layout === "force") {
36831
- const maxIterations = 300;
36832
- const COOL = 0.12;
36833
- const COLLIDE_PASSES = 6;
36834
- const LABEL_GAP = 12;
36835
- const LABEL_H = 16;
36836
- const SIMILARITY_NEIGHBORS = 5;
36837
- const SIMILARITY_MIN_WEIGHT = 0.25;
36838
- const drawnPairs = /* @__PURE__ */ new Set();
36839
- for (const edge of propEdges) drawnPairs.add(edgeKeyOf(edge.source, edge.target));
36840
- const similarityNeighbors = /* @__PURE__ */ new Map();
36841
- {
36837
+ if (fullRelayout) {
36838
+ const LABEL_GAP = 12;
36839
+ const LABEL_H = 16;
36840
+ for (const n of simNodes) n.labelW = showLabels && n.label ? measureLabelWidth(truncateLabel(n.label), labelFont) : 0;
36841
+ const SIMILARITY_NEIGHBORS = 5;
36842
+ const SIMILARITY_MIN_WEIGHT = 0.25;
36843
+ const drawnPairs = /* @__PURE__ */ new Set();
36844
+ for (const edge of propEdges) drawnPairs.add(edgeKeyOf(edge.source, edge.target));
36845
+ const similarityNeighbors = /* @__PURE__ */ new Map();
36842
36846
  const bySource = /* @__PURE__ */ new Map();
36843
36847
  for (const pair of propSimilarity) {
36844
36848
  if (pair.weight < SIMILARITY_MIN_WEIGHT) continue;
@@ -36849,113 +36853,33 @@ var init_GraphCanvas = __esm({
36849
36853
  }
36850
36854
  for (const [id, arr] of bySource) {
36851
36855
  arr.sort((a, b) => b.weight - a.weight);
36852
- const keep = new Set(arr.slice(0, SIMILARITY_NEIGHBORS).map((p) => p.target));
36853
- similarityNeighbors.set(id, keep);
36856
+ similarityNeighbors.set(id, new Set(arr.slice(0, SIMILARITY_NEIGHBORS).map((p) => p.target)));
36854
36857
  }
36855
- }
36856
- const activeSimilarity = propSimilarity.filter((pair) => {
36857
- if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) return false;
36858
- const a = similarityNeighbors.get(pair.source);
36859
- const b = similarityNeighbors.get(pair.target);
36860
- return (a?.has(pair.target) ?? false) || (b?.has(pair.source) ?? false);
36861
- });
36862
- const tick = (iter, options) => {
36863
- const skipForces = options?.skipForces ?? false;
36864
- const nodes = nodesRef.current;
36865
- const centerX = w / 2;
36866
- const centerY = h / 2;
36867
- const temp = skipForces ? 0 : Math.max(COOL, 1 - iter / maxIterations);
36868
- for (const node of nodes) {
36869
- node.fx = 0;
36870
- node.fy = 0;
36871
- }
36872
- if (!skipForces) {
36873
- for (let i = 0; i < nodes.length; i++) {
36874
- for (let j = i + 1; j < nodes.length; j++) {
36875
- const dx = nodes[j].x - nodes[i].x;
36876
- const dy = nodes[j].y - nodes[i].y;
36877
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
36878
- const force = repulsion / (dist * dist) * temp;
36879
- const fx = dx / dist * force;
36880
- const fy = dy / dist * force;
36881
- nodes[i].fx -= fx;
36882
- nodes[i].fy -= fy;
36883
- nodes[j].fx += fx;
36884
- nodes[j].fy += fy;
36885
- }
36886
- }
36887
- const nodeById = new Map(nodes.map((n) => [n.id, n]));
36888
- const spring = (a, b, rest, k) => {
36889
- const dx = b.x - a.x;
36890
- const dy = b.y - a.y;
36891
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
36892
- const force = (dist - rest) * k * temp;
36893
- const fx = dx / dist * force;
36894
- const fy = dy / dist * force;
36895
- a.fx += fx;
36896
- a.fy += fy;
36897
- b.fx -= fx;
36898
- b.fy -= fy;
36899
- };
36900
- for (const edge of propEdges) {
36901
- const source = nodeById.get(edge.source);
36902
- const target = nodeById.get(edge.target);
36903
- if (!source || !target) continue;
36904
- drawnPairs.add(edgeKeyOf(edge.source, edge.target));
36905
- const w2 = Math.min(1, Math.max(0, edge.weight ?? 1));
36906
- spring(source, target, linkDistance * (0.35 + (1 - w2) * 0.3), 0.14);
36907
- }
36908
- for (const pair of activeSimilarity) {
36909
- const source = nodeById.get(pair.source);
36910
- const target = nodeById.get(pair.target);
36911
- if (!source || !target) continue;
36912
- const w2 = Math.min(1, Math.max(0, pair.weight));
36913
- spring(source, target, linkDistance * (1.35 - 0.55 * w2), 0.015);
36914
- }
36915
- const centroids = /* @__PURE__ */ new Map();
36916
- for (const node of nodes) {
36917
- const g = node.group ?? "__none__";
36918
- let c = centroids.get(g);
36919
- if (!c) {
36920
- c = { x: 0, y: 0, n: 0 };
36921
- centroids.set(g, c);
36922
- }
36923
- c.x += node.x;
36924
- c.y += node.y;
36925
- c.n += 1;
36926
- }
36927
- const multiCluster = centroids.size > 1;
36928
- for (const node of nodes) {
36929
- if (multiCluster) {
36930
- const c = centroids.get(node.group ?? "__none__");
36931
- if (c && c.n > 1) {
36932
- node.fx += (c.x / c.n - node.x) * 0.06 * temp;
36933
- node.fy += (c.y / c.n - node.y) * 0.06 * temp;
36934
- } else {
36935
- node.fx += (centerX - node.x) * 0.01 * temp;
36936
- node.fy += (centerY - node.y) * 0.01 * temp;
36937
- }
36938
- } else {
36939
- node.fx += (centerX - node.x) * 8e-3 * temp;
36940
- node.fy += (centerY - node.y) * 8e-3 * temp;
36941
- }
36942
- }
36943
- const damping = 0.9;
36944
- for (const node of nodes) {
36945
- node.vx = (node.vx + node.fx) * damping;
36946
- node.vy = (node.vy + node.fy) * damping;
36947
- node.x += node.vx;
36948
- node.y += node.vy;
36949
- }
36950
- }
36951
- for (const node of nodes) {
36952
- node.x = Math.max(30, Math.min(w - 30, node.x));
36953
- node.y = Math.max(30, Math.min(h - 30, node.y));
36858
+ const activeSimilarity = propSimilarity.filter((pair) => {
36859
+ if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) return false;
36860
+ const a = similarityNeighbors.get(pair.source);
36861
+ const b = similarityNeighbors.get(pair.target);
36862
+ return (a?.has(pair.target) ?? false) || (b?.has(pair.source) ?? false);
36863
+ });
36864
+ const groupTarget = /* @__PURE__ */ new Map();
36865
+ const multiCluster = groups.length > 1;
36866
+ if (multiCluster) {
36867
+ const ring = Math.min(w, h) * 0.34;
36868
+ groups.forEach((g, i) => {
36869
+ const a = i / groups.length * 2 * Math.PI;
36870
+ groupTarget.set(g, { x: w / 2 + ring * Math.cos(a), y: h / 2 + ring * Math.sin(a) });
36871
+ });
36954
36872
  }
36873
+ const present = new Set(simNodes.map((n) => n.id));
36874
+ 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) }));
36875
+ 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) }));
36876
+ const collideRadius = (n) => Math.max(n.size || 8, (n.labelW ?? 0) / 2) + nodeSpacing / 2;
36877
+ 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();
36878
+ for (let i = 0; i < 300; i++) simulation.tick();
36955
36879
  const pad = nodeSpacing / 2;
36956
36880
  const rectsOf = (n) => {
36957
36881
  const r = n.size || 8;
36958
- const lw = showLabels ? n.labelW ?? (n.labelW = n.label ? measureLabelWidth(n.label, labelFont) : 0) : 0;
36882
+ const lw = showLabels ? n.labelW ?? 0 : 0;
36959
36883
  return {
36960
36884
  circle: [n.x - r - pad, n.y - r - pad, n.x + r + pad, n.y + r + pad],
36961
36885
  label: [n.x - lw / 2 - pad, n.y + r + LABEL_GAP - 8, n.x + lw / 2 + pad, n.y + r + LABEL_GAP + LABEL_H]
@@ -36967,11 +36891,12 @@ var init_GraphCanvas = __esm({
36967
36891
  if (ox <= 0 || oy <= 0) return null;
36968
36892
  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 };
36969
36893
  };
36970
- for (let pass = 0; pass < COLLIDE_PASSES; pass++) {
36971
- for (let i = 0; i < nodes.length; i++) {
36972
- for (let j = i + 1; j < nodes.length; j++) {
36973
- const a = nodes[i];
36974
- const b = nodes[j];
36894
+ for (let pass = 0; pass < 24; pass++) {
36895
+ let moved = false;
36896
+ for (let i = 0; i < simNodes.length; i++) {
36897
+ for (let j = i + 1; j < simNodes.length; j++) {
36898
+ const a = simNodes[i];
36899
+ const b = simNodes[j];
36975
36900
  const ra = rectsOf(a);
36976
36901
  const rb = rectsOf(b);
36977
36902
  let best = null;
@@ -36980,31 +36905,25 @@ var init_GraphCanvas = __esm({
36980
36905
  if (s && (!best || s.depth < best.depth)) best = s;
36981
36906
  }
36982
36907
  if (best) {
36908
+ moved = true;
36983
36909
  const push = best.depth / 2;
36984
36910
  if (best.axis === "x") {
36985
36911
  a.x = Math.max(30, Math.min(w - 30, a.x - push * best.sign));
36986
36912
  b.x = Math.max(30, Math.min(w - 30, b.x + push * best.sign));
36987
- a.vx = 0;
36988
- b.vx = 0;
36989
36913
  } else {
36990
36914
  a.y = Math.max(30, Math.min(h - 30, a.y - push * best.sign));
36991
36915
  b.y = Math.max(30, Math.min(h - 30, b.y + push * best.sign));
36992
- a.vy = 0;
36993
- b.vy = 0;
36994
36916
  }
36995
36917
  }
36996
36918
  }
36997
36919
  }
36920
+ if (!moved) break;
36998
36921
  }
36999
- };
37000
- if (fullRelayout) {
37001
- for (let i = 0; i < maxIterations; i++) tick(i);
37002
- for (let i = 0; i < 4; i++) tick(maxIterations, { skipForces: true });
37003
36922
  const fitPad = 40;
37004
36923
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
37005
36924
  for (const node of nodesRef.current) {
37006
36925
  const r = node.size || 8;
37007
- const lw = showLabels ? node.labelW ?? (node.labelW = node.label ? measureLabelWidth(node.label, labelFont) : 0) : 0;
36926
+ const lw = showLabels ? node.labelW ?? 0 : 0;
37008
36927
  minX = Math.min(minX, node.x - r, node.x - lw / 2);
37009
36928
  minY = Math.min(minY, node.y - r);
37010
36929
  maxX = Math.max(maxX, node.x + r, node.x + lw / 2);
@@ -37125,16 +37044,17 @@ var init_GraphCanvas = __esm({
37125
37044
  }
37126
37045
  ctx.stroke();
37127
37046
  if (showLabels && node.label) {
37128
- ctx.font = `${isSelected || isHovered ? "600" : "500"} 12px ${fontFamily}`;
37047
+ const displayLabel = truncateLabel(node.label);
37048
+ ctx.font = `${isSelected || isHovered ? "700" : "600"} 12px ${fontFamily}`;
37129
37049
  ctx.textAlign = "center";
37130
37050
  ctx.textBaseline = "middle";
37131
37051
  const ly = node.y + radius + 14;
37132
- ctx.lineWidth = 3;
37052
+ ctx.lineWidth = 4;
37133
37053
  ctx.lineJoin = "round";
37134
37054
  ctx.strokeStyle = bgColor;
37135
- ctx.strokeText(node.label, node.x, ly);
37136
- ctx.fillStyle = isSelected || isHovered ? fgColor : mutedColor;
37137
- ctx.fillText(node.label, node.x, ly);
37055
+ ctx.strokeText(displayLabel, node.x, ly);
37056
+ ctx.fillStyle = fgColor;
37057
+ ctx.fillText(displayLabel, node.x, ly);
37138
37058
  }
37139
37059
  if (node.badge && node.badge > 1) {
37140
37060
  const bx = node.x + radius * 0.7;
@@ -37282,6 +37202,20 @@ var init_GraphCanvas = __esm({
37282
37202
  },
37283
37203
  [toCoords, nodeAt, onNodeDoubleClick]
37284
37204
  );
37205
+ const hoveredObj = hoveredNode ? nodesRef.current.find((n) => n.id === hoveredNode) : void 0;
37206
+ const canvasEl = canvasRef.current;
37207
+ const showLabelTooltip = Boolean(
37208
+ hoveredObj?.label && hoveredObj.label.length > MAX_LABEL_CHARS && hoveredObj.x != null && canvasEl
37209
+ );
37210
+ const labelTooltipStyle = (() => {
37211
+ if (!showLabelTooltip || !canvasEl || !hoveredObj || hoveredObj.x == null || hoveredObj.y == null) return void 0;
37212
+ const rect = canvasEl.getBoundingClientRect();
37213
+ return {
37214
+ left: rect.left + offset.x + hoveredObj.x * zoom,
37215
+ top: rect.top + offset.y + hoveredObj.y * zoom - 10,
37216
+ transform: "translate(-50%, -100%)"
37217
+ };
37218
+ })();
37285
37219
  if (isLoading) {
37286
37220
  return /* @__PURE__ */ jsxRuntime.jsx(exports.LoadingState, { message: t("common.loading"), className });
37287
37221
  }
@@ -37335,23 +37269,43 @@ var init_GraphCanvas = __esm({
37335
37269
  ]
37336
37270
  }
37337
37271
  ),
37338
- /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "w-full bg-background", children: /* @__PURE__ */ jsxRuntime.jsx(
37339
- "canvas",
37340
- {
37341
- ref: canvasRef,
37342
- width: Math.round(logicalW * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
37343
- height: Math.round(height * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
37344
- className: "w-full cursor-grab active:cursor-grabbing touch-none",
37345
- style: { height },
37346
- onPointerDown: gestureHandlers.onPointerDown,
37347
- onPointerMove: gestureHandlers.onPointerMove,
37348
- onPointerUp: gestureHandlers.onPointerUp,
37349
- onPointerCancel: gestureHandlers.onPointerCancel,
37350
- onPointerLeave: handlePointerLeave,
37351
- onWheel: gestureHandlers.onWheel,
37352
- onDoubleClick: handleDoubleClick
37353
- }
37354
- ) }),
37272
+ /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { className: "w-full bg-background", children: [
37273
+ /* @__PURE__ */ jsxRuntime.jsx(
37274
+ "canvas",
37275
+ {
37276
+ ref: canvasRef,
37277
+ width: Math.round(logicalW * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
37278
+ height: Math.round(height * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
37279
+ className: "w-full cursor-grab active:cursor-grabbing touch-none",
37280
+ style: { height },
37281
+ onPointerDown: gestureHandlers.onPointerDown,
37282
+ onPointerMove: gestureHandlers.onPointerMove,
37283
+ onPointerUp: gestureHandlers.onPointerUp,
37284
+ onPointerCancel: gestureHandlers.onPointerCancel,
37285
+ onPointerLeave: handlePointerLeave,
37286
+ onWheel: gestureHandlers.onWheel,
37287
+ onDoubleClick: handleDoubleClick
37288
+ }
37289
+ ),
37290
+ typeof window !== "undefined" && showLabelTooltip && labelTooltipStyle && reactDom.createPortal(
37291
+ /* @__PURE__ */ jsxRuntime.jsx(
37292
+ "div",
37293
+ {
37294
+ className: cn(
37295
+ "fixed z-50 px-3 py-2 max-w-xs",
37296
+ "bg-primary text-primary-foreground",
37297
+ "shadow-elevation-popover rounded-sm",
37298
+ "text-sm pointer-events-none",
37299
+ "break-words whitespace-normal"
37300
+ ),
37301
+ style: labelTooltipStyle,
37302
+ role: "tooltip",
37303
+ children: hoveredObj?.label
37304
+ }
37305
+ ),
37306
+ document.body
37307
+ )
37308
+ ] }),
37355
37309
  groups.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(exports.HStack, { gap: "md", className: "px-4 py-2 border-t border-border", wrap: true, children: groups.map((group, idx) => /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "xs", align: "center", children: [
37356
37310
  /* @__PURE__ */ jsxRuntime.jsx(
37357
37311
  exports.Box,