@almadar/ui 5.119.0 → 5.121.0

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.
package/dist/avl/index.js CHANGED
@@ -40,6 +40,7 @@ import { FieldTypeSchema, isInlineTrait, isPageReference, buildResolvedTraitConf
40
40
  import { useDroppable, useDraggable, DndContext, DragOverlay, useSensors, useSensor, PointerSensor, KeyboardSensor, pointerWithin, rectIntersection, closestCorners } from '@dnd-kit/core';
41
41
  import { sortableKeyboardCoordinates, useSortable, arrayMove, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
42
42
  import { CSS } from '@dnd-kit/utilities';
43
+ import { forceSimulation, forceLink, forceManyBody, forceCollide, forceX, forceY } from 'd3-force';
43
44
  import { isDrawHostPattern, getPatternDefinition, getComponentForPattern as getComponentForPattern$1, isEntityAwarePattern } from '@almadar/core/patterns';
44
45
  import { OrbitalServerRuntime } from '@almadar/runtime/OrbitalServerRuntime';
45
46
  import { isKnownStdOperator } from '@almadar/std/registry';
@@ -37229,7 +37230,10 @@ function resolveColor3(color, el) {
37229
37230
  const resolved = getComputedStyle(el).getPropertyValue(m[1]).trim();
37230
37231
  return resolved || (m[2]?.trim() ?? "#888888");
37231
37232
  }
37232
- var GROUP_COLORS2, labelMeasureCtx, GraphCanvas;
37233
+ function truncateLabel(label) {
37234
+ return label.length > MAX_LABEL_CHARS ? label.slice(0, MAX_LABEL_CHARS - 1) + "\u2026" : label;
37235
+ }
37236
+ var GROUP_COLORS2, labelMeasureCtx, MAX_LABEL_CHARS, GraphCanvas;
37233
37237
  var init_GraphCanvas = __esm({
37234
37238
  "components/core/molecules/GraphCanvas.tsx"() {
37235
37239
  "use client";
@@ -37250,6 +37254,7 @@ var init_GraphCanvas = __esm({
37250
37254
  "var(--color-accent)"
37251
37255
  ];
37252
37256
  labelMeasureCtx = null;
37257
+ MAX_LABEL_CHARS = 22;
37253
37258
  GraphCanvas = ({
37254
37259
  title,
37255
37260
  nodes: propNodes = [],
@@ -37354,9 +37359,13 @@ var init_GraphCanvas = __esm({
37354
37359
  useEffect(() => {
37355
37360
  const canvas = canvasRef.current;
37356
37361
  if (!canvas || propNodes.length === 0) return;
37357
- const w = logicalW;
37358
- const h = height;
37362
+ const viewW = logicalW;
37363
+ const viewH = height;
37364
+ const layoutScale = Math.max(1, Math.min(3, Math.sqrt(propNodes.length / 12)));
37365
+ const w = viewW * layoutScale;
37366
+ const h = viewH * layoutScale;
37359
37367
  const labelFont = resolveColor3("var(--font-family)", canvas) || "system-ui";
37368
+ const clamp01 = (v) => Math.min(1, Math.max(0, v));
37360
37369
  const prevPos = /* @__PURE__ */ new Map();
37361
37370
  for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
37362
37371
  const simNodes = propNodes.map((n, idx) => {
@@ -37381,7 +37390,7 @@ var init_GraphCanvas = __esm({
37381
37390
  y = h * 0.2 + rand() * h * 0.6;
37382
37391
  }
37383
37392
  }
37384
- return { ...n, x, y, vx: 0, vy: 0, fx: 0, fy: 0 };
37393
+ return { ...n, x, y, vx: 0, vy: 0 };
37385
37394
  });
37386
37395
  nodesRef.current = simNodes;
37387
37396
  const prevIds = /* @__PURE__ */ new Set([...prevPos.keys()]);
@@ -37390,105 +37399,52 @@ var init_GraphCanvas = __esm({
37390
37399
  const overlap = prevIds.size === 0 ? 0 : kept / Math.max(prevIds.size, newIdList.length);
37391
37400
  const fullRelayout = !laidOutRef.current || overlap < 0.5;
37392
37401
  if (layout === "force") {
37393
- const maxIterations = 300;
37394
- const COOL = 0.12;
37395
- const COLLIDE_PASSES = 6;
37396
- const LABEL_GAP = 12;
37397
- const LABEL_H = 16;
37398
- const tick = (iter) => {
37399
- const nodes = nodesRef.current;
37400
- const centerX = w / 2;
37401
- const centerY = h / 2;
37402
- const temp = Math.max(COOL, 1 - iter / maxIterations);
37403
- for (const node of nodes) {
37404
- node.fx = 0;
37405
- node.fy = 0;
37406
- }
37407
- for (let i = 0; i < nodes.length; i++) {
37408
- for (let j = i + 1; j < nodes.length; j++) {
37409
- const dx = nodes[j].x - nodes[i].x;
37410
- const dy = nodes[j].y - nodes[i].y;
37411
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37412
- const force = repulsion / (dist * dist) * temp;
37413
- const fx = dx / dist * force;
37414
- const fy = dy / dist * force;
37415
- nodes[i].fx -= fx;
37416
- nodes[i].fy -= fy;
37417
- nodes[j].fx += fx;
37418
- nodes[j].fy += fy;
37419
- }
37420
- }
37421
- const nodeById = new Map(nodes.map((n) => [n.id, n]));
37422
- const spring = (a, b, rest, k) => {
37423
- const dx = b.x - a.x;
37424
- const dy = b.y - a.y;
37425
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37426
- const force = (dist - rest) * k * temp;
37427
- const fx = dx / dist * force;
37428
- const fy = dy / dist * force;
37429
- a.fx += fx;
37430
- a.fy += fy;
37431
- b.fx -= fx;
37432
- b.fy -= fy;
37433
- };
37402
+ if (fullRelayout) {
37403
+ const LABEL_GAP = 12;
37404
+ const LABEL_H = 16;
37405
+ for (const n of simNodes) n.labelW = showLabels && n.label ? measureLabelWidth(truncateLabel(n.label), labelFont) : 0;
37406
+ const SIMILARITY_NEIGHBORS = 5;
37407
+ const SIMILARITY_MIN_WEIGHT = 0.25;
37434
37408
  const drawnPairs = /* @__PURE__ */ new Set();
37435
- for (const edge of propEdges) {
37436
- const source = nodeById.get(edge.source);
37437
- const target = nodeById.get(edge.target);
37438
- if (!source || !target) continue;
37439
- drawnPairs.add(edgeKeyOf(edge.source, edge.target));
37440
- const w2 = Math.min(1, Math.max(0, edge.weight ?? 1));
37441
- spring(source, target, linkDistance * (0.35 + (1 - w2) * 0.3), 0.14);
37442
- }
37409
+ for (const edge of propEdges) drawnPairs.add(edgeKeyOf(edge.source, edge.target));
37410
+ const similarityNeighbors = /* @__PURE__ */ new Map();
37411
+ const bySource = /* @__PURE__ */ new Map();
37443
37412
  for (const pair of propSimilarity) {
37413
+ if (pair.weight < SIMILARITY_MIN_WEIGHT) continue;
37444
37414
  if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) continue;
37445
- const source = nodeById.get(pair.source);
37446
- const target = nodeById.get(pair.target);
37447
- if (!source || !target) continue;
37448
- const w2 = Math.min(1, Math.max(0, pair.weight));
37449
- spring(source, target, linkDistance * (1.35 - 0.55 * w2), 0.015);
37415
+ const arr = bySource.get(pair.source) ?? [];
37416
+ arr.push(pair);
37417
+ bySource.set(pair.source, arr);
37450
37418
  }
37451
- const centroids = /* @__PURE__ */ new Map();
37452
- for (const node of nodes) {
37453
- const g = node.group ?? "__none__";
37454
- let c = centroids.get(g);
37455
- if (!c) {
37456
- c = { x: 0, y: 0, n: 0 };
37457
- centroids.set(g, c);
37458
- }
37459
- c.x += node.x;
37460
- c.y += node.y;
37461
- c.n += 1;
37419
+ for (const [id, arr] of bySource) {
37420
+ arr.sort((a, b) => b.weight - a.weight);
37421
+ similarityNeighbors.set(id, new Set(arr.slice(0, SIMILARITY_NEIGHBORS).map((p) => p.target)));
37462
37422
  }
37463
- const multiCluster = centroids.size > 1;
37464
- for (const node of nodes) {
37465
- if (multiCluster) {
37466
- const c = centroids.get(node.group ?? "__none__");
37467
- if (c && c.n > 1) {
37468
- node.fx += (c.x / c.n - node.x) * 0.04 * temp;
37469
- node.fy += (c.y / c.n - node.y) * 0.04 * temp;
37470
- } else {
37471
- node.fx += (centerX - node.x) * 0.01 * temp;
37472
- node.fy += (centerY - node.y) * 0.01 * temp;
37473
- }
37474
- } else {
37475
- node.fx += (centerX - node.x) * 8e-3 * temp;
37476
- node.fy += (centerY - node.y) * 8e-3 * temp;
37477
- }
37478
- }
37479
- const damping = 0.9;
37480
- for (const node of nodes) {
37481
- node.vx = (node.vx + node.fx) * damping;
37482
- node.vy = (node.vy + node.fy) * damping;
37483
- node.x += node.vx;
37484
- node.y += node.vy;
37485
- node.x = Math.max(30, Math.min(w - 30, node.x));
37486
- node.y = Math.max(30, Math.min(h - 30, node.y));
37423
+ const activeSimilarity = propSimilarity.filter((pair) => {
37424
+ if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) return false;
37425
+ const a = similarityNeighbors.get(pair.source);
37426
+ const b = similarityNeighbors.get(pair.target);
37427
+ return (a?.has(pair.target) ?? false) || (b?.has(pair.source) ?? false);
37428
+ });
37429
+ const groupTarget = /* @__PURE__ */ new Map();
37430
+ const multiCluster = groups.length > 1;
37431
+ if (multiCluster) {
37432
+ const ring = Math.min(w, h) * 0.34;
37433
+ groups.forEach((g, i) => {
37434
+ const a = i / groups.length * 2 * Math.PI;
37435
+ groupTarget.set(g, { x: w / 2 + ring * Math.cos(a), y: h / 2 + ring * Math.sin(a) });
37436
+ });
37487
37437
  }
37438
+ const present = new Set(simNodes.map((n) => n.id));
37439
+ 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) }));
37440
+ 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) }));
37441
+ const collideRadius = (n) => Math.max(n.size || 8, (n.labelW ?? 0) / 2) + nodeSpacing / 2;
37442
+ 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();
37443
+ for (let i = 0; i < 300; i++) simulation.tick();
37488
37444
  const pad = nodeSpacing / 2;
37489
37445
  const rectsOf = (n) => {
37490
37446
  const r2 = n.size || 8;
37491
- const lw = showLabels ? n.labelW ?? (n.labelW = n.label ? measureLabelWidth(n.label, labelFont) : 0) : 0;
37447
+ const lw = showLabels ? n.labelW ?? 0 : 0;
37492
37448
  return {
37493
37449
  circle: [n.x - r2 - pad, n.y - r2 - pad, n.x + r2 + pad, n.y + r2 + pad],
37494
37450
  label: [n.x - lw / 2 - pad, n.y + r2 + LABEL_GAP - 8, n.x + lw / 2 + pad, n.y + r2 + LABEL_GAP + LABEL_H]
@@ -37500,11 +37456,12 @@ var init_GraphCanvas = __esm({
37500
37456
  if (ox <= 0 || oy <= 0) return null;
37501
37457
  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 };
37502
37458
  };
37503
- for (let pass = 0; pass < COLLIDE_PASSES; pass++) {
37504
- for (let i = 0; i < nodes.length; i++) {
37505
- for (let j = i + 1; j < nodes.length; j++) {
37506
- const a = nodes[i];
37507
- const b = nodes[j];
37459
+ for (let pass = 0; pass < 24; pass++) {
37460
+ let moved = false;
37461
+ for (let i = 0; i < simNodes.length; i++) {
37462
+ for (let j = i + 1; j < simNodes.length; j++) {
37463
+ const a = simNodes[i];
37464
+ const b = simNodes[j];
37508
37465
  const ra = rectsOf(a);
37509
37466
  const rb = rectsOf(b);
37510
37467
  let best = null;
@@ -37513,25 +37470,35 @@ var init_GraphCanvas = __esm({
37513
37470
  if (s && (!best || s.depth < best.depth)) best = s;
37514
37471
  }
37515
37472
  if (best) {
37473
+ moved = true;
37516
37474
  const push = best.depth / 2;
37517
37475
  if (best.axis === "x") {
37518
37476
  a.x = Math.max(30, Math.min(w - 30, a.x - push * best.sign));
37519
37477
  b.x = Math.max(30, Math.min(w - 30, b.x + push * best.sign));
37520
- a.vx = 0;
37521
- b.vx = 0;
37522
37478
  } else {
37523
37479
  a.y = Math.max(30, Math.min(h - 30, a.y - push * best.sign));
37524
37480
  b.y = Math.max(30, Math.min(h - 30, b.y + push * best.sign));
37525
- a.vy = 0;
37526
- b.vy = 0;
37527
37481
  }
37528
37482
  }
37529
37483
  }
37530
37484
  }
37485
+ if (!moved) break;
37531
37486
  }
37532
- };
37533
- if (fullRelayout) {
37534
- for (let i = 0; i < maxIterations; i++) tick(i);
37487
+ const fitPad = 40;
37488
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
37489
+ for (const node of nodesRef.current) {
37490
+ const r2 = node.size || 8;
37491
+ const lw = showLabels ? node.labelW ?? 0 : 0;
37492
+ minX = Math.min(minX, node.x - r2, node.x - lw / 2);
37493
+ minY = Math.min(minY, node.y - r2);
37494
+ maxX = Math.max(maxX, node.x + r2, node.x + lw / 2);
37495
+ maxY = Math.max(maxY, node.y + r2 + LABEL_GAP + LABEL_H);
37496
+ }
37497
+ const bboxW = Math.max(1, maxX - minX + fitPad * 2);
37498
+ const bboxH = Math.max(1, maxY - minY + fitPad * 2);
37499
+ const nextZoom = Math.min(1, viewW / bboxW, viewH / bboxH);
37500
+ setZoom(nextZoom);
37501
+ setOffset({ x: fitPad - minX * nextZoom, y: fitPad - minY * nextZoom });
37535
37502
  laidOutRef.current = true;
37536
37503
  } else {
37537
37504
  const centroids = /* @__PURE__ */ new Map();
@@ -37642,6 +37609,7 @@ var init_GraphCanvas = __esm({
37642
37609
  }
37643
37610
  ctx.stroke();
37644
37611
  if (showLabels && node.label) {
37612
+ const displayLabel = truncateLabel(node.label);
37645
37613
  ctx.font = `${isSelected || isHovered ? "600" : "500"} 12px ${fontFamily}`;
37646
37614
  ctx.textAlign = "center";
37647
37615
  ctx.textBaseline = "middle";
@@ -37649,9 +37617,9 @@ var init_GraphCanvas = __esm({
37649
37617
  ctx.lineWidth = 3;
37650
37618
  ctx.lineJoin = "round";
37651
37619
  ctx.strokeStyle = bgColor;
37652
- ctx.strokeText(node.label, node.x, ly);
37620
+ ctx.strokeText(displayLabel, node.x, ly);
37653
37621
  ctx.fillStyle = isSelected || isHovered ? fgColor : mutedColor;
37654
- ctx.fillText(node.label, node.x, ly);
37622
+ ctx.fillText(displayLabel, node.x, ly);
37655
37623
  }
37656
37624
  if (node.badge && node.badge > 1) {
37657
37625
  const bx = node.x + radius * 0.7;
@@ -37799,6 +37767,20 @@ var init_GraphCanvas = __esm({
37799
37767
  },
37800
37768
  [toCoords, nodeAt, onNodeDoubleClick]
37801
37769
  );
37770
+ const hoveredObj = hoveredNode ? nodesRef.current.find((n) => n.id === hoveredNode) : void 0;
37771
+ const canvasEl = canvasRef.current;
37772
+ const showLabelTooltip = Boolean(
37773
+ hoveredObj?.label && hoveredObj.label.length > MAX_LABEL_CHARS && hoveredObj.x != null && canvasEl
37774
+ );
37775
+ const labelTooltipStyle = (() => {
37776
+ if (!showLabelTooltip || !canvasEl || !hoveredObj || hoveredObj.x == null || hoveredObj.y == null) return void 0;
37777
+ const rect = canvasEl.getBoundingClientRect();
37778
+ return {
37779
+ left: rect.left + offset.x + hoveredObj.x * zoom,
37780
+ top: rect.top + offset.y + hoveredObj.y * zoom - 10,
37781
+ transform: "translate(-50%, -100%)"
37782
+ };
37783
+ })();
37802
37784
  if (isLoading) {
37803
37785
  return /* @__PURE__ */ jsx(LoadingState, { message: t("common.loading"), className });
37804
37786
  }
@@ -37852,23 +37834,43 @@ var init_GraphCanvas = __esm({
37852
37834
  ]
37853
37835
  }
37854
37836
  ),
37855
- /* @__PURE__ */ jsx(Box, { className: "w-full bg-background", children: /* @__PURE__ */ jsx(
37856
- "canvas",
37857
- {
37858
- ref: canvasRef,
37859
- width: Math.round(logicalW * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
37860
- height: Math.round(height * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
37861
- className: "w-full cursor-grab active:cursor-grabbing touch-none",
37862
- style: { height },
37863
- onPointerDown: gestureHandlers.onPointerDown,
37864
- onPointerMove: gestureHandlers.onPointerMove,
37865
- onPointerUp: gestureHandlers.onPointerUp,
37866
- onPointerCancel: gestureHandlers.onPointerCancel,
37867
- onPointerLeave: handlePointerLeave,
37868
- onWheel: gestureHandlers.onWheel,
37869
- onDoubleClick: handleDoubleClick
37870
- }
37871
- ) }),
37837
+ /* @__PURE__ */ jsxs(Box, { className: "w-full bg-background", children: [
37838
+ /* @__PURE__ */ jsx(
37839
+ "canvas",
37840
+ {
37841
+ ref: canvasRef,
37842
+ width: Math.round(logicalW * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
37843
+ height: Math.round(height * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
37844
+ className: "w-full cursor-grab active:cursor-grabbing touch-none",
37845
+ style: { height },
37846
+ onPointerDown: gestureHandlers.onPointerDown,
37847
+ onPointerMove: gestureHandlers.onPointerMove,
37848
+ onPointerUp: gestureHandlers.onPointerUp,
37849
+ onPointerCancel: gestureHandlers.onPointerCancel,
37850
+ onPointerLeave: handlePointerLeave,
37851
+ onWheel: gestureHandlers.onWheel,
37852
+ onDoubleClick: handleDoubleClick
37853
+ }
37854
+ ),
37855
+ typeof window !== "undefined" && showLabelTooltip && labelTooltipStyle && createPortal(
37856
+ /* @__PURE__ */ jsx(
37857
+ "div",
37858
+ {
37859
+ className: cn(
37860
+ "fixed z-50 px-3 py-2 max-w-xs",
37861
+ "bg-primary text-primary-foreground",
37862
+ "shadow-elevation-popover rounded-sm",
37863
+ "text-sm pointer-events-none",
37864
+ "break-words whitespace-normal"
37865
+ ),
37866
+ style: labelTooltipStyle,
37867
+ role: "tooltip",
37868
+ children: hoveredObj?.label
37869
+ }
37870
+ ),
37871
+ document.body
37872
+ )
37873
+ ] }),
37872
37874
  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: [
37873
37875
  /* @__PURE__ */ jsx(
37874
37876
  Box,