@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.
@@ -44,6 +44,7 @@ import { DndContext, pointerWithin, rectIntersection, closestCorners, useSensors
44
44
  import { useSortable, arrayMove, sortableKeyboardCoordinates, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
45
45
  import { CSS } from '@dnd-kit/utilities';
46
46
  import { useNodeId, ReactFlowProvider, Handle, Position } from '@xyflow/react';
47
+ import { forceSimulation, forceLink, forceManyBody, forceCollide, forceX, forceY } from 'd3-force';
47
48
  import { isDrawHostPattern, getPatternDefinition, getComponentForPattern as getComponentForPattern$1 } from '@almadar/core/patterns';
48
49
  import { StateMachineManager, collectDeclaredConfigDefaults, resolveCallSitePayloadCaptures, createServerEffectHandlers, EffectExecutor, createContextFromBindings, createTickScheduler, isValidCronExpression, parseDurationString, InMemoryPersistence, normalizeCallSiteConfigToValues } from '@almadar/runtime';
49
50
  import { isKnownStdOperator } from '@almadar/std/registry';
@@ -34726,7 +34727,10 @@ function resolveColor3(color, el) {
34726
34727
  const resolved = getComputedStyle(el).getPropertyValue(m[1]).trim();
34727
34728
  return resolved || (m[2]?.trim() ?? "#888888");
34728
34729
  }
34729
- var GROUP_COLORS2, labelMeasureCtx, GraphCanvas;
34730
+ function truncateLabel(label) {
34731
+ return label.length > MAX_LABEL_CHARS ? label.slice(0, MAX_LABEL_CHARS - 1) + "\u2026" : label;
34732
+ }
34733
+ var GROUP_COLORS2, labelMeasureCtx, MAX_LABEL_CHARS, GraphCanvas;
34730
34734
  var init_GraphCanvas = __esm({
34731
34735
  "components/core/molecules/GraphCanvas.tsx"() {
34732
34736
  "use client";
@@ -34747,6 +34751,7 @@ var init_GraphCanvas = __esm({
34747
34751
  "var(--color-accent)"
34748
34752
  ];
34749
34753
  labelMeasureCtx = null;
34754
+ MAX_LABEL_CHARS = 22;
34750
34755
  GraphCanvas = ({
34751
34756
  title,
34752
34757
  nodes: propNodes = [],
@@ -34857,6 +34862,7 @@ var init_GraphCanvas = __esm({
34857
34862
  const w = viewW * layoutScale;
34858
34863
  const h = viewH * layoutScale;
34859
34864
  const labelFont = resolveColor3("var(--font-family)", canvas) || "system-ui";
34865
+ const clamp01 = (v) => Math.min(1, Math.max(0, v));
34860
34866
  const prevPos = /* @__PURE__ */ new Map();
34861
34867
  for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
34862
34868
  const simNodes = propNodes.map((n, idx) => {
@@ -34881,7 +34887,7 @@ var init_GraphCanvas = __esm({
34881
34887
  y = h * 0.2 + rand() * h * 0.6;
34882
34888
  }
34883
34889
  }
34884
- return { ...n, x, y, vx: 0, vy: 0, fx: 0, fy: 0 };
34890
+ return { ...n, x, y, vx: 0, vy: 0 };
34885
34891
  });
34886
34892
  nodesRef.current = simNodes;
34887
34893
  const prevIds = /* @__PURE__ */ new Set([...prevPos.keys()]);
@@ -34890,17 +34896,15 @@ var init_GraphCanvas = __esm({
34890
34896
  const overlap = prevIds.size === 0 ? 0 : kept / Math.max(prevIds.size, newIdList.length);
34891
34897
  const fullRelayout = !laidOutRef.current || overlap < 0.5;
34892
34898
  if (layout === "force") {
34893
- const maxIterations = 300;
34894
- const COOL = 0.12;
34895
- const COLLIDE_PASSES = 6;
34896
- const LABEL_GAP = 12;
34897
- const LABEL_H = 16;
34898
- const SIMILARITY_NEIGHBORS = 5;
34899
- const SIMILARITY_MIN_WEIGHT = 0.25;
34900
- const drawnPairs = /* @__PURE__ */ new Set();
34901
- for (const edge of propEdges) drawnPairs.add(edgeKeyOf(edge.source, edge.target));
34902
- const similarityNeighbors = /* @__PURE__ */ new Map();
34903
- {
34899
+ if (fullRelayout) {
34900
+ const LABEL_GAP = 12;
34901
+ const LABEL_H = 16;
34902
+ for (const n of simNodes) n.labelW = showLabels && n.label ? measureLabelWidth(truncateLabel(n.label), labelFont) : 0;
34903
+ const SIMILARITY_NEIGHBORS = 5;
34904
+ const SIMILARITY_MIN_WEIGHT = 0.25;
34905
+ const drawnPairs = /* @__PURE__ */ new Set();
34906
+ for (const edge of propEdges) drawnPairs.add(edgeKeyOf(edge.source, edge.target));
34907
+ const similarityNeighbors = /* @__PURE__ */ new Map();
34904
34908
  const bySource = /* @__PURE__ */ new Map();
34905
34909
  for (const pair of propSimilarity) {
34906
34910
  if (pair.weight < SIMILARITY_MIN_WEIGHT) continue;
@@ -34911,113 +34915,33 @@ var init_GraphCanvas = __esm({
34911
34915
  }
34912
34916
  for (const [id, arr] of bySource) {
34913
34917
  arr.sort((a, b) => b.weight - a.weight);
34914
- const keep = new Set(arr.slice(0, SIMILARITY_NEIGHBORS).map((p) => p.target));
34915
- similarityNeighbors.set(id, keep);
34918
+ similarityNeighbors.set(id, new Set(arr.slice(0, SIMILARITY_NEIGHBORS).map((p) => p.target)));
34916
34919
  }
34917
- }
34918
- const activeSimilarity = propSimilarity.filter((pair) => {
34919
- if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) return false;
34920
- const a = similarityNeighbors.get(pair.source);
34921
- const b = similarityNeighbors.get(pair.target);
34922
- return (a?.has(pair.target) ?? false) || (b?.has(pair.source) ?? false);
34923
- });
34924
- const tick = (iter, options) => {
34925
- const skipForces = options?.skipForces ?? false;
34926
- const nodes = nodesRef.current;
34927
- const centerX = w / 2;
34928
- const centerY = h / 2;
34929
- const temp = skipForces ? 0 : Math.max(COOL, 1 - iter / maxIterations);
34930
- for (const node of nodes) {
34931
- node.fx = 0;
34932
- node.fy = 0;
34933
- }
34934
- if (!skipForces) {
34935
- for (let i = 0; i < nodes.length; i++) {
34936
- for (let j = i + 1; j < nodes.length; j++) {
34937
- const dx = nodes[j].x - nodes[i].x;
34938
- const dy = nodes[j].y - nodes[i].y;
34939
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
34940
- const force = repulsion / (dist * dist) * temp;
34941
- const fx = dx / dist * force;
34942
- const fy = dy / dist * force;
34943
- nodes[i].fx -= fx;
34944
- nodes[i].fy -= fy;
34945
- nodes[j].fx += fx;
34946
- nodes[j].fy += fy;
34947
- }
34948
- }
34949
- const nodeById = new Map(nodes.map((n) => [n.id, n]));
34950
- const spring = (a, b, rest, k) => {
34951
- const dx = b.x - a.x;
34952
- const dy = b.y - a.y;
34953
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
34954
- const force = (dist - rest) * k * temp;
34955
- const fx = dx / dist * force;
34956
- const fy = dy / dist * force;
34957
- a.fx += fx;
34958
- a.fy += fy;
34959
- b.fx -= fx;
34960
- b.fy -= fy;
34961
- };
34962
- for (const edge of propEdges) {
34963
- const source = nodeById.get(edge.source);
34964
- const target = nodeById.get(edge.target);
34965
- if (!source || !target) continue;
34966
- drawnPairs.add(edgeKeyOf(edge.source, edge.target));
34967
- const w2 = Math.min(1, Math.max(0, edge.weight ?? 1));
34968
- spring(source, target, linkDistance * (0.35 + (1 - w2) * 0.3), 0.14);
34969
- }
34970
- for (const pair of activeSimilarity) {
34971
- const source = nodeById.get(pair.source);
34972
- const target = nodeById.get(pair.target);
34973
- if (!source || !target) continue;
34974
- const w2 = Math.min(1, Math.max(0, pair.weight));
34975
- spring(source, target, linkDistance * (1.35 - 0.55 * w2), 0.015);
34976
- }
34977
- const centroids = /* @__PURE__ */ new Map();
34978
- for (const node of nodes) {
34979
- const g = node.group ?? "__none__";
34980
- let c = centroids.get(g);
34981
- if (!c) {
34982
- c = { x: 0, y: 0, n: 0 };
34983
- centroids.set(g, c);
34984
- }
34985
- c.x += node.x;
34986
- c.y += node.y;
34987
- c.n += 1;
34988
- }
34989
- const multiCluster = centroids.size > 1;
34990
- for (const node of nodes) {
34991
- if (multiCluster) {
34992
- const c = centroids.get(node.group ?? "__none__");
34993
- if (c && c.n > 1) {
34994
- node.fx += (c.x / c.n - node.x) * 0.06 * temp;
34995
- node.fy += (c.y / c.n - node.y) * 0.06 * temp;
34996
- } else {
34997
- node.fx += (centerX - node.x) * 0.01 * temp;
34998
- node.fy += (centerY - node.y) * 0.01 * temp;
34999
- }
35000
- } else {
35001
- node.fx += (centerX - node.x) * 8e-3 * temp;
35002
- node.fy += (centerY - node.y) * 8e-3 * temp;
35003
- }
35004
- }
35005
- const damping = 0.9;
35006
- for (const node of nodes) {
35007
- node.vx = (node.vx + node.fx) * damping;
35008
- node.vy = (node.vy + node.fy) * damping;
35009
- node.x += node.vx;
35010
- node.y += node.vy;
35011
- }
35012
- }
35013
- for (const node of nodes) {
35014
- node.x = Math.max(30, Math.min(w - 30, node.x));
35015
- node.y = Math.max(30, Math.min(h - 30, node.y));
34920
+ const activeSimilarity = propSimilarity.filter((pair) => {
34921
+ if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) return false;
34922
+ const a = similarityNeighbors.get(pair.source);
34923
+ const b = similarityNeighbors.get(pair.target);
34924
+ return (a?.has(pair.target) ?? false) || (b?.has(pair.source) ?? false);
34925
+ });
34926
+ const groupTarget = /* @__PURE__ */ new Map();
34927
+ const multiCluster = groups.length > 1;
34928
+ if (multiCluster) {
34929
+ const ring = Math.min(w, h) * 0.34;
34930
+ groups.forEach((g, i) => {
34931
+ const a = i / groups.length * 2 * Math.PI;
34932
+ groupTarget.set(g, { x: w / 2 + ring * Math.cos(a), y: h / 2 + ring * Math.sin(a) });
34933
+ });
35016
34934
  }
34935
+ const present = new Set(simNodes.map((n) => n.id));
34936
+ 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) }));
34937
+ 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) }));
34938
+ const collideRadius = (n) => Math.max(n.size || 8, (n.labelW ?? 0) / 2) + nodeSpacing / 2;
34939
+ 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();
34940
+ for (let i = 0; i < 300; i++) simulation.tick();
35017
34941
  const pad = nodeSpacing / 2;
35018
34942
  const rectsOf = (n) => {
35019
34943
  const r = n.size || 8;
35020
- const lw = showLabels ? n.labelW ?? (n.labelW = n.label ? measureLabelWidth(n.label, labelFont) : 0) : 0;
34944
+ const lw = showLabels ? n.labelW ?? 0 : 0;
35021
34945
  return {
35022
34946
  circle: [n.x - r - pad, n.y - r - pad, n.x + r + pad, n.y + r + pad],
35023
34947
  label: [n.x - lw / 2 - pad, n.y + r + LABEL_GAP - 8, n.x + lw / 2 + pad, n.y + r + LABEL_GAP + LABEL_H]
@@ -35029,11 +34953,12 @@ var init_GraphCanvas = __esm({
35029
34953
  if (ox <= 0 || oy <= 0) return null;
35030
34954
  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 };
35031
34955
  };
35032
- for (let pass = 0; pass < COLLIDE_PASSES; pass++) {
35033
- for (let i = 0; i < nodes.length; i++) {
35034
- for (let j = i + 1; j < nodes.length; j++) {
35035
- const a = nodes[i];
35036
- const b = nodes[j];
34956
+ for (let pass = 0; pass < 24; pass++) {
34957
+ let moved = false;
34958
+ for (let i = 0; i < simNodes.length; i++) {
34959
+ for (let j = i + 1; j < simNodes.length; j++) {
34960
+ const a = simNodes[i];
34961
+ const b = simNodes[j];
35037
34962
  const ra = rectsOf(a);
35038
34963
  const rb = rectsOf(b);
35039
34964
  let best = null;
@@ -35042,31 +34967,25 @@ var init_GraphCanvas = __esm({
35042
34967
  if (s && (!best || s.depth < best.depth)) best = s;
35043
34968
  }
35044
34969
  if (best) {
34970
+ moved = true;
35045
34971
  const push = best.depth / 2;
35046
34972
  if (best.axis === "x") {
35047
34973
  a.x = Math.max(30, Math.min(w - 30, a.x - push * best.sign));
35048
34974
  b.x = Math.max(30, Math.min(w - 30, b.x + push * best.sign));
35049
- a.vx = 0;
35050
- b.vx = 0;
35051
34975
  } else {
35052
34976
  a.y = Math.max(30, Math.min(h - 30, a.y - push * best.sign));
35053
34977
  b.y = Math.max(30, Math.min(h - 30, b.y + push * best.sign));
35054
- a.vy = 0;
35055
- b.vy = 0;
35056
34978
  }
35057
34979
  }
35058
34980
  }
35059
34981
  }
34982
+ if (!moved) break;
35060
34983
  }
35061
- };
35062
- if (fullRelayout) {
35063
- for (let i = 0; i < maxIterations; i++) tick(i);
35064
- for (let i = 0; i < 4; i++) tick(maxIterations, { skipForces: true });
35065
34984
  const fitPad = 40;
35066
34985
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
35067
34986
  for (const node of nodesRef.current) {
35068
34987
  const r = node.size || 8;
35069
- const lw = showLabels ? node.labelW ?? (node.labelW = node.label ? measureLabelWidth(node.label, labelFont) : 0) : 0;
34988
+ const lw = showLabels ? node.labelW ?? 0 : 0;
35070
34989
  minX = Math.min(minX, node.x - r, node.x - lw / 2);
35071
34990
  minY = Math.min(minY, node.y - r);
35072
34991
  maxX = Math.max(maxX, node.x + r, node.x + lw / 2);
@@ -35187,16 +35106,17 @@ var init_GraphCanvas = __esm({
35187
35106
  }
35188
35107
  ctx.stroke();
35189
35108
  if (showLabels && node.label) {
35190
- ctx.font = `${isSelected || isHovered ? "600" : "500"} 12px ${fontFamily}`;
35109
+ const displayLabel = truncateLabel(node.label);
35110
+ ctx.font = `${isSelected || isHovered ? "700" : "600"} 12px ${fontFamily}`;
35191
35111
  ctx.textAlign = "center";
35192
35112
  ctx.textBaseline = "middle";
35193
35113
  const ly = node.y + radius + 14;
35194
- ctx.lineWidth = 3;
35114
+ ctx.lineWidth = 4;
35195
35115
  ctx.lineJoin = "round";
35196
35116
  ctx.strokeStyle = bgColor;
35197
- ctx.strokeText(node.label, node.x, ly);
35198
- ctx.fillStyle = isSelected || isHovered ? fgColor : mutedColor;
35199
- ctx.fillText(node.label, node.x, ly);
35117
+ ctx.strokeText(displayLabel, node.x, ly);
35118
+ ctx.fillStyle = fgColor;
35119
+ ctx.fillText(displayLabel, node.x, ly);
35200
35120
  }
35201
35121
  if (node.badge && node.badge > 1) {
35202
35122
  const bx = node.x + radius * 0.7;
@@ -35344,6 +35264,20 @@ var init_GraphCanvas = __esm({
35344
35264
  },
35345
35265
  [toCoords, nodeAt, onNodeDoubleClick]
35346
35266
  );
35267
+ const hoveredObj = hoveredNode ? nodesRef.current.find((n) => n.id === hoveredNode) : void 0;
35268
+ const canvasEl = canvasRef.current;
35269
+ const showLabelTooltip = Boolean(
35270
+ hoveredObj?.label && hoveredObj.label.length > MAX_LABEL_CHARS && hoveredObj.x != null && canvasEl
35271
+ );
35272
+ const labelTooltipStyle = (() => {
35273
+ if (!showLabelTooltip || !canvasEl || !hoveredObj || hoveredObj.x == null || hoveredObj.y == null) return void 0;
35274
+ const rect = canvasEl.getBoundingClientRect();
35275
+ return {
35276
+ left: rect.left + offset.x + hoveredObj.x * zoom,
35277
+ top: rect.top + offset.y + hoveredObj.y * zoom - 10,
35278
+ transform: "translate(-50%, -100%)"
35279
+ };
35280
+ })();
35347
35281
  if (isLoading) {
35348
35282
  return /* @__PURE__ */ jsx(LoadingState, { message: t("common.loading"), className });
35349
35283
  }
@@ -35397,23 +35331,43 @@ var init_GraphCanvas = __esm({
35397
35331
  ]
35398
35332
  }
35399
35333
  ),
35400
- /* @__PURE__ */ jsx(Box, { className: "w-full bg-background", children: /* @__PURE__ */ jsx(
35401
- "canvas",
35402
- {
35403
- ref: canvasRef,
35404
- width: Math.round(logicalW * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
35405
- height: Math.round(height * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
35406
- className: "w-full cursor-grab active:cursor-grabbing touch-none",
35407
- style: { height },
35408
- onPointerDown: gestureHandlers.onPointerDown,
35409
- onPointerMove: gestureHandlers.onPointerMove,
35410
- onPointerUp: gestureHandlers.onPointerUp,
35411
- onPointerCancel: gestureHandlers.onPointerCancel,
35412
- onPointerLeave: handlePointerLeave,
35413
- onWheel: gestureHandlers.onWheel,
35414
- onDoubleClick: handleDoubleClick
35415
- }
35416
- ) }),
35334
+ /* @__PURE__ */ jsxs(Box, { className: "w-full bg-background", children: [
35335
+ /* @__PURE__ */ jsx(
35336
+ "canvas",
35337
+ {
35338
+ ref: canvasRef,
35339
+ width: Math.round(logicalW * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
35340
+ height: Math.round(height * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
35341
+ className: "w-full cursor-grab active:cursor-grabbing touch-none",
35342
+ style: { height },
35343
+ onPointerDown: gestureHandlers.onPointerDown,
35344
+ onPointerMove: gestureHandlers.onPointerMove,
35345
+ onPointerUp: gestureHandlers.onPointerUp,
35346
+ onPointerCancel: gestureHandlers.onPointerCancel,
35347
+ onPointerLeave: handlePointerLeave,
35348
+ onWheel: gestureHandlers.onWheel,
35349
+ onDoubleClick: handleDoubleClick
35350
+ }
35351
+ ),
35352
+ typeof window !== "undefined" && showLabelTooltip && labelTooltipStyle && createPortal(
35353
+ /* @__PURE__ */ jsx(
35354
+ "div",
35355
+ {
35356
+ className: cn(
35357
+ "fixed z-50 px-3 py-2 max-w-xs",
35358
+ "bg-primary text-primary-foreground",
35359
+ "shadow-elevation-popover rounded-sm",
35360
+ "text-sm pointer-events-none",
35361
+ "break-words whitespace-normal"
35362
+ ),
35363
+ style: labelTooltipStyle,
35364
+ role: "tooltip",
35365
+ children: hoveredObj?.label
35366
+ }
35367
+ ),
35368
+ document.body
35369
+ )
35370
+ ] }),
35417
35371
  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: [
35418
35372
  /* @__PURE__ */ jsx(
35419
35373
  Box,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.120.0",
3
+ "version": "5.121.1",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -130,6 +130,7 @@
130
130
  "@tabler/icons-react": "^3.44.0",
131
131
  "@xyflow/react": "12.10.1",
132
132
  "clsx": "^2.1.0",
133
+ "d3-force": "^3.0.0",
133
134
  "elkjs": "0.11.1",
134
135
  "hastscript": "^9.0.0",
135
136
  "leaflet": "1.9.4",
@@ -179,6 +180,7 @@
179
180
  "@testing-library/jest-dom": "^6.4.0",
180
181
  "@testing-library/react": "^14.2.0",
181
182
  "@testing-library/user-event": "^14.5.0",
183
+ "@types/d3-force": "^3.0.10",
182
184
  "@types/leaflet": "1.9.21",
183
185
  "@types/node": "^22.0.0",
184
186
  "@types/react": "^19.0.0",