@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.
@@ -43,6 +43,7 @@ import { DndContext, useSensors, useSensor, PointerSensor, KeyboardSensor, useDr
43
43
  import { useSortable, arrayMove, sortableKeyboardCoordinates, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
44
44
  import { CSS } from '@dnd-kit/utilities';
45
45
  import { useNodeId, ReactFlowProvider, Handle, Position } from '@xyflow/react';
46
+ import { forceSimulation, forceLink, forceManyBody, forceCollide, forceX, forceY } from 'd3-force';
46
47
  import { isDrawHostPattern, getPatternDefinition as getPatternDefinition$1, getComponentForPattern as getComponentForPattern$1 } from '@almadar/core/patterns';
47
48
  import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
48
49
 
@@ -36619,7 +36620,10 @@ function resolveColor3(color, el) {
36619
36620
  const resolved = getComputedStyle(el).getPropertyValue(m[1]).trim();
36620
36621
  return resolved || (m[2]?.trim() ?? "#888888");
36621
36622
  }
36622
- var GROUP_COLORS2, labelMeasureCtx, GraphCanvas;
36623
+ function truncateLabel(label) {
36624
+ return label.length > MAX_LABEL_CHARS ? label.slice(0, MAX_LABEL_CHARS - 1) + "\u2026" : label;
36625
+ }
36626
+ var GROUP_COLORS2, labelMeasureCtx, MAX_LABEL_CHARS, GraphCanvas;
36623
36627
  var init_GraphCanvas = __esm({
36624
36628
  "components/core/molecules/GraphCanvas.tsx"() {
36625
36629
  "use client";
@@ -36640,6 +36644,7 @@ var init_GraphCanvas = __esm({
36640
36644
  "var(--color-accent)"
36641
36645
  ];
36642
36646
  labelMeasureCtx = null;
36647
+ MAX_LABEL_CHARS = 22;
36643
36648
  GraphCanvas = ({
36644
36649
  title,
36645
36650
  nodes: propNodes = [],
@@ -36750,6 +36755,7 @@ var init_GraphCanvas = __esm({
36750
36755
  const w = viewW * layoutScale;
36751
36756
  const h = viewH * layoutScale;
36752
36757
  const labelFont = resolveColor3("var(--font-family)", canvas) || "system-ui";
36758
+ const clamp01 = (v) => Math.min(1, Math.max(0, v));
36753
36759
  const prevPos = /* @__PURE__ */ new Map();
36754
36760
  for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
36755
36761
  const simNodes = propNodes.map((n, idx) => {
@@ -36774,7 +36780,7 @@ var init_GraphCanvas = __esm({
36774
36780
  y = h * 0.2 + rand() * h * 0.6;
36775
36781
  }
36776
36782
  }
36777
- return { ...n, x, y, vx: 0, vy: 0, fx: 0, fy: 0 };
36783
+ return { ...n, x, y, vx: 0, vy: 0 };
36778
36784
  });
36779
36785
  nodesRef.current = simNodes;
36780
36786
  const prevIds = /* @__PURE__ */ new Set([...prevPos.keys()]);
@@ -36783,17 +36789,15 @@ var init_GraphCanvas = __esm({
36783
36789
  const overlap = prevIds.size === 0 ? 0 : kept / Math.max(prevIds.size, newIdList.length);
36784
36790
  const fullRelayout = !laidOutRef.current || overlap < 0.5;
36785
36791
  if (layout === "force") {
36786
- const maxIterations = 300;
36787
- const COOL = 0.12;
36788
- const COLLIDE_PASSES = 6;
36789
- const LABEL_GAP = 12;
36790
- const LABEL_H = 16;
36791
- const SIMILARITY_NEIGHBORS = 5;
36792
- const SIMILARITY_MIN_WEIGHT = 0.25;
36793
- const drawnPairs = /* @__PURE__ */ new Set();
36794
- for (const edge of propEdges) drawnPairs.add(edgeKeyOf(edge.source, edge.target));
36795
- const similarityNeighbors = /* @__PURE__ */ new Map();
36796
- {
36792
+ if (fullRelayout) {
36793
+ const LABEL_GAP = 12;
36794
+ const LABEL_H = 16;
36795
+ for (const n of simNodes) n.labelW = showLabels && n.label ? measureLabelWidth(truncateLabel(n.label), labelFont) : 0;
36796
+ const SIMILARITY_NEIGHBORS = 5;
36797
+ const SIMILARITY_MIN_WEIGHT = 0.25;
36798
+ const drawnPairs = /* @__PURE__ */ new Set();
36799
+ for (const edge of propEdges) drawnPairs.add(edgeKeyOf(edge.source, edge.target));
36800
+ const similarityNeighbors = /* @__PURE__ */ new Map();
36797
36801
  const bySource = /* @__PURE__ */ new Map();
36798
36802
  for (const pair of propSimilarity) {
36799
36803
  if (pair.weight < SIMILARITY_MIN_WEIGHT) continue;
@@ -36804,113 +36808,33 @@ var init_GraphCanvas = __esm({
36804
36808
  }
36805
36809
  for (const [id, arr] of bySource) {
36806
36810
  arr.sort((a, b) => b.weight - a.weight);
36807
- const keep = new Set(arr.slice(0, SIMILARITY_NEIGHBORS).map((p) => p.target));
36808
- similarityNeighbors.set(id, keep);
36811
+ similarityNeighbors.set(id, new Set(arr.slice(0, SIMILARITY_NEIGHBORS).map((p) => p.target)));
36809
36812
  }
36810
- }
36811
- const activeSimilarity = propSimilarity.filter((pair) => {
36812
- if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) return false;
36813
- const a = similarityNeighbors.get(pair.source);
36814
- const b = similarityNeighbors.get(pair.target);
36815
- return (a?.has(pair.target) ?? false) || (b?.has(pair.source) ?? false);
36816
- });
36817
- const tick = (iter, options) => {
36818
- const skipForces = options?.skipForces ?? false;
36819
- const nodes = nodesRef.current;
36820
- const centerX = w / 2;
36821
- const centerY = h / 2;
36822
- const temp = skipForces ? 0 : Math.max(COOL, 1 - iter / maxIterations);
36823
- for (const node of nodes) {
36824
- node.fx = 0;
36825
- node.fy = 0;
36826
- }
36827
- if (!skipForces) {
36828
- for (let i = 0; i < nodes.length; i++) {
36829
- for (let j = i + 1; j < nodes.length; j++) {
36830
- const dx = nodes[j].x - nodes[i].x;
36831
- const dy = nodes[j].y - nodes[i].y;
36832
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
36833
- const force = repulsion / (dist * dist) * temp;
36834
- const fx = dx / dist * force;
36835
- const fy = dy / dist * force;
36836
- nodes[i].fx -= fx;
36837
- nodes[i].fy -= fy;
36838
- nodes[j].fx += fx;
36839
- nodes[j].fy += fy;
36840
- }
36841
- }
36842
- const nodeById = new Map(nodes.map((n) => [n.id, n]));
36843
- const spring = (a, b, rest, k) => {
36844
- const dx = b.x - a.x;
36845
- const dy = b.y - a.y;
36846
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
36847
- const force = (dist - rest) * k * temp;
36848
- const fx = dx / dist * force;
36849
- const fy = dy / dist * force;
36850
- a.fx += fx;
36851
- a.fy += fy;
36852
- b.fx -= fx;
36853
- b.fy -= fy;
36854
- };
36855
- for (const edge of propEdges) {
36856
- const source = nodeById.get(edge.source);
36857
- const target = nodeById.get(edge.target);
36858
- if (!source || !target) continue;
36859
- drawnPairs.add(edgeKeyOf(edge.source, edge.target));
36860
- const w2 = Math.min(1, Math.max(0, edge.weight ?? 1));
36861
- spring(source, target, linkDistance * (0.35 + (1 - w2) * 0.3), 0.14);
36862
- }
36863
- for (const pair of activeSimilarity) {
36864
- const source = nodeById.get(pair.source);
36865
- const target = nodeById.get(pair.target);
36866
- if (!source || !target) continue;
36867
- const w2 = Math.min(1, Math.max(0, pair.weight));
36868
- spring(source, target, linkDistance * (1.35 - 0.55 * w2), 0.015);
36869
- }
36870
- const centroids = /* @__PURE__ */ new Map();
36871
- for (const node of nodes) {
36872
- const g = node.group ?? "__none__";
36873
- let c = centroids.get(g);
36874
- if (!c) {
36875
- c = { x: 0, y: 0, n: 0 };
36876
- centroids.set(g, c);
36877
- }
36878
- c.x += node.x;
36879
- c.y += node.y;
36880
- c.n += 1;
36881
- }
36882
- const multiCluster = centroids.size > 1;
36883
- for (const node of nodes) {
36884
- if (multiCluster) {
36885
- const c = centroids.get(node.group ?? "__none__");
36886
- if (c && c.n > 1) {
36887
- node.fx += (c.x / c.n - node.x) * 0.06 * temp;
36888
- node.fy += (c.y / c.n - node.y) * 0.06 * temp;
36889
- } else {
36890
- node.fx += (centerX - node.x) * 0.01 * temp;
36891
- node.fy += (centerY - node.y) * 0.01 * temp;
36892
- }
36893
- } else {
36894
- node.fx += (centerX - node.x) * 8e-3 * temp;
36895
- node.fy += (centerY - node.y) * 8e-3 * temp;
36896
- }
36897
- }
36898
- const damping = 0.9;
36899
- for (const node of nodes) {
36900
- node.vx = (node.vx + node.fx) * damping;
36901
- node.vy = (node.vy + node.fy) * damping;
36902
- node.x += node.vx;
36903
- node.y += node.vy;
36904
- }
36905
- }
36906
- for (const node of nodes) {
36907
- node.x = Math.max(30, Math.min(w - 30, node.x));
36908
- node.y = Math.max(30, Math.min(h - 30, node.y));
36813
+ const activeSimilarity = propSimilarity.filter((pair) => {
36814
+ if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) return false;
36815
+ const a = similarityNeighbors.get(pair.source);
36816
+ const b = similarityNeighbors.get(pair.target);
36817
+ return (a?.has(pair.target) ?? false) || (b?.has(pair.source) ?? false);
36818
+ });
36819
+ const groupTarget = /* @__PURE__ */ new Map();
36820
+ const multiCluster = groups.length > 1;
36821
+ if (multiCluster) {
36822
+ const ring = Math.min(w, h) * 0.34;
36823
+ groups.forEach((g, i) => {
36824
+ const a = i / groups.length * 2 * Math.PI;
36825
+ groupTarget.set(g, { x: w / 2 + ring * Math.cos(a), y: h / 2 + ring * Math.sin(a) });
36826
+ });
36909
36827
  }
36828
+ const present = new Set(simNodes.map((n) => n.id));
36829
+ 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) }));
36830
+ 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) }));
36831
+ const collideRadius = (n) => Math.max(n.size || 8, (n.labelW ?? 0) / 2) + nodeSpacing / 2;
36832
+ const simulation = forceSimulation(simNodes).force("edge", forceLink(edgeLinks).id((d) => d.id).distance((l) => linkDistance * (0.35 + (1 - l.weight) * 0.3)).strength(0.9)).force("similarity", forceLink(simLinks).id((d) => d.id).distance((l) => linkDistance * (1.35 - 0.55 * l.weight)).strength(0.03)).force("charge", forceManyBody().strength(-repulsion * 0.5)).force("collide", forceCollide().radius(collideRadius).strength(0.9)).force("x", forceX((n) => groupTarget.get(n.group ?? "")?.x ?? w / 2).strength(multiCluster ? 0.05 : 0.02)).force("y", forceY((n) => groupTarget.get(n.group ?? "")?.y ?? h / 2).strength(multiCluster ? 0.05 : 0.02)).stop();
36833
+ for (let i = 0; i < 300; i++) simulation.tick();
36910
36834
  const pad = nodeSpacing / 2;
36911
36835
  const rectsOf = (n) => {
36912
36836
  const r = n.size || 8;
36913
- const lw = showLabels ? n.labelW ?? (n.labelW = n.label ? measureLabelWidth(n.label, labelFont) : 0) : 0;
36837
+ const lw = showLabels ? n.labelW ?? 0 : 0;
36914
36838
  return {
36915
36839
  circle: [n.x - r - pad, n.y - r - pad, n.x + r + pad, n.y + r + pad],
36916
36840
  label: [n.x - lw / 2 - pad, n.y + r + LABEL_GAP - 8, n.x + lw / 2 + pad, n.y + r + LABEL_GAP + LABEL_H]
@@ -36922,11 +36846,12 @@ var init_GraphCanvas = __esm({
36922
36846
  if (ox <= 0 || oy <= 0) return null;
36923
36847
  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 };
36924
36848
  };
36925
- for (let pass = 0; pass < COLLIDE_PASSES; pass++) {
36926
- for (let i = 0; i < nodes.length; i++) {
36927
- for (let j = i + 1; j < nodes.length; j++) {
36928
- const a = nodes[i];
36929
- const b = nodes[j];
36849
+ for (let pass = 0; pass < 24; pass++) {
36850
+ let moved = false;
36851
+ for (let i = 0; i < simNodes.length; i++) {
36852
+ for (let j = i + 1; j < simNodes.length; j++) {
36853
+ const a = simNodes[i];
36854
+ const b = simNodes[j];
36930
36855
  const ra = rectsOf(a);
36931
36856
  const rb = rectsOf(b);
36932
36857
  let best = null;
@@ -36935,31 +36860,25 @@ var init_GraphCanvas = __esm({
36935
36860
  if (s && (!best || s.depth < best.depth)) best = s;
36936
36861
  }
36937
36862
  if (best) {
36863
+ moved = true;
36938
36864
  const push = best.depth / 2;
36939
36865
  if (best.axis === "x") {
36940
36866
  a.x = Math.max(30, Math.min(w - 30, a.x - push * best.sign));
36941
36867
  b.x = Math.max(30, Math.min(w - 30, b.x + push * best.sign));
36942
- a.vx = 0;
36943
- b.vx = 0;
36944
36868
  } else {
36945
36869
  a.y = Math.max(30, Math.min(h - 30, a.y - push * best.sign));
36946
36870
  b.y = Math.max(30, Math.min(h - 30, b.y + push * best.sign));
36947
- a.vy = 0;
36948
- b.vy = 0;
36949
36871
  }
36950
36872
  }
36951
36873
  }
36952
36874
  }
36875
+ if (!moved) break;
36953
36876
  }
36954
- };
36955
- if (fullRelayout) {
36956
- for (let i = 0; i < maxIterations; i++) tick(i);
36957
- for (let i = 0; i < 4; i++) tick(maxIterations, { skipForces: true });
36958
36877
  const fitPad = 40;
36959
36878
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
36960
36879
  for (const node of nodesRef.current) {
36961
36880
  const r = node.size || 8;
36962
- const lw = showLabels ? node.labelW ?? (node.labelW = node.label ? measureLabelWidth(node.label, labelFont) : 0) : 0;
36881
+ const lw = showLabels ? node.labelW ?? 0 : 0;
36963
36882
  minX = Math.min(minX, node.x - r, node.x - lw / 2);
36964
36883
  minY = Math.min(minY, node.y - r);
36965
36884
  maxX = Math.max(maxX, node.x + r, node.x + lw / 2);
@@ -37080,16 +36999,17 @@ var init_GraphCanvas = __esm({
37080
36999
  }
37081
37000
  ctx.stroke();
37082
37001
  if (showLabels && node.label) {
37083
- ctx.font = `${isSelected || isHovered ? "600" : "500"} 12px ${fontFamily}`;
37002
+ const displayLabel = truncateLabel(node.label);
37003
+ ctx.font = `${isSelected || isHovered ? "700" : "600"} 12px ${fontFamily}`;
37084
37004
  ctx.textAlign = "center";
37085
37005
  ctx.textBaseline = "middle";
37086
37006
  const ly = node.y + radius + 14;
37087
- ctx.lineWidth = 3;
37007
+ ctx.lineWidth = 4;
37088
37008
  ctx.lineJoin = "round";
37089
37009
  ctx.strokeStyle = bgColor;
37090
- ctx.strokeText(node.label, node.x, ly);
37091
- ctx.fillStyle = isSelected || isHovered ? fgColor : mutedColor;
37092
- ctx.fillText(node.label, node.x, ly);
37010
+ ctx.strokeText(displayLabel, node.x, ly);
37011
+ ctx.fillStyle = fgColor;
37012
+ ctx.fillText(displayLabel, node.x, ly);
37093
37013
  }
37094
37014
  if (node.badge && node.badge > 1) {
37095
37015
  const bx = node.x + radius * 0.7;
@@ -37237,6 +37157,20 @@ var init_GraphCanvas = __esm({
37237
37157
  },
37238
37158
  [toCoords, nodeAt, onNodeDoubleClick]
37239
37159
  );
37160
+ const hoveredObj = hoveredNode ? nodesRef.current.find((n) => n.id === hoveredNode) : void 0;
37161
+ const canvasEl = canvasRef.current;
37162
+ const showLabelTooltip = Boolean(
37163
+ hoveredObj?.label && hoveredObj.label.length > MAX_LABEL_CHARS && hoveredObj.x != null && canvasEl
37164
+ );
37165
+ const labelTooltipStyle = (() => {
37166
+ if (!showLabelTooltip || !canvasEl || !hoveredObj || hoveredObj.x == null || hoveredObj.y == null) return void 0;
37167
+ const rect = canvasEl.getBoundingClientRect();
37168
+ return {
37169
+ left: rect.left + offset.x + hoveredObj.x * zoom,
37170
+ top: rect.top + offset.y + hoveredObj.y * zoom - 10,
37171
+ transform: "translate(-50%, -100%)"
37172
+ };
37173
+ })();
37240
37174
  if (isLoading) {
37241
37175
  return /* @__PURE__ */ jsx(LoadingState, { message: t("common.loading"), className });
37242
37176
  }
@@ -37290,23 +37224,43 @@ var init_GraphCanvas = __esm({
37290
37224
  ]
37291
37225
  }
37292
37226
  ),
37293
- /* @__PURE__ */ jsx(Box, { className: "w-full bg-background", children: /* @__PURE__ */ jsx(
37294
- "canvas",
37295
- {
37296
- ref: canvasRef,
37297
- width: Math.round(logicalW * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
37298
- height: Math.round(height * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
37299
- className: "w-full cursor-grab active:cursor-grabbing touch-none",
37300
- style: { height },
37301
- onPointerDown: gestureHandlers.onPointerDown,
37302
- onPointerMove: gestureHandlers.onPointerMove,
37303
- onPointerUp: gestureHandlers.onPointerUp,
37304
- onPointerCancel: gestureHandlers.onPointerCancel,
37305
- onPointerLeave: handlePointerLeave,
37306
- onWheel: gestureHandlers.onWheel,
37307
- onDoubleClick: handleDoubleClick
37308
- }
37309
- ) }),
37227
+ /* @__PURE__ */ jsxs(Box, { className: "w-full bg-background", children: [
37228
+ /* @__PURE__ */ jsx(
37229
+ "canvas",
37230
+ {
37231
+ ref: canvasRef,
37232
+ width: Math.round(logicalW * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
37233
+ height: Math.round(height * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
37234
+ className: "w-full cursor-grab active:cursor-grabbing touch-none",
37235
+ style: { height },
37236
+ onPointerDown: gestureHandlers.onPointerDown,
37237
+ onPointerMove: gestureHandlers.onPointerMove,
37238
+ onPointerUp: gestureHandlers.onPointerUp,
37239
+ onPointerCancel: gestureHandlers.onPointerCancel,
37240
+ onPointerLeave: handlePointerLeave,
37241
+ onWheel: gestureHandlers.onWheel,
37242
+ onDoubleClick: handleDoubleClick
37243
+ }
37244
+ ),
37245
+ typeof window !== "undefined" && showLabelTooltip && labelTooltipStyle && createPortal(
37246
+ /* @__PURE__ */ jsx(
37247
+ "div",
37248
+ {
37249
+ className: cn(
37250
+ "fixed z-50 px-3 py-2 max-w-xs",
37251
+ "bg-primary text-primary-foreground",
37252
+ "shadow-elevation-popover rounded-sm",
37253
+ "text-sm pointer-events-none",
37254
+ "break-words whitespace-normal"
37255
+ ),
37256
+ style: labelTooltipStyle,
37257
+ role: "tooltip",
37258
+ children: hoveredObj?.label
37259
+ }
37260
+ ),
37261
+ document.body
37262
+ )
37263
+ ] }),
37310
37264
  groups.length > 1 && /* @__PURE__ */ jsx(HStack, { gap: "md", className: "px-4 py-2 border-t border-border", wrap: true, children: groups.map((group, idx) => /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
37311
37265
  /* @__PURE__ */ jsx(
37312
37266
  Box,