@almadar/ui 5.120.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 = [],
@@ -37360,6 +37365,7 @@ var init_GraphCanvas = __esm({
37360
37365
  const w = viewW * layoutScale;
37361
37366
  const h = viewH * layoutScale;
37362
37367
  const labelFont = resolveColor3("var(--font-family)", canvas) || "system-ui";
37368
+ const clamp01 = (v) => Math.min(1, Math.max(0, v));
37363
37369
  const prevPos = /* @__PURE__ */ new Map();
37364
37370
  for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
37365
37371
  const simNodes = propNodes.map((n, idx) => {
@@ -37384,7 +37390,7 @@ var init_GraphCanvas = __esm({
37384
37390
  y = h * 0.2 + rand() * h * 0.6;
37385
37391
  }
37386
37392
  }
37387
- return { ...n, x, y, vx: 0, vy: 0, fx: 0, fy: 0 };
37393
+ return { ...n, x, y, vx: 0, vy: 0 };
37388
37394
  });
37389
37395
  nodesRef.current = simNodes;
37390
37396
  const prevIds = /* @__PURE__ */ new Set([...prevPos.keys()]);
@@ -37393,17 +37399,15 @@ var init_GraphCanvas = __esm({
37393
37399
  const overlap = prevIds.size === 0 ? 0 : kept / Math.max(prevIds.size, newIdList.length);
37394
37400
  const fullRelayout = !laidOutRef.current || overlap < 0.5;
37395
37401
  if (layout === "force") {
37396
- const maxIterations = 300;
37397
- const COOL = 0.12;
37398
- const COLLIDE_PASSES = 6;
37399
- const LABEL_GAP = 12;
37400
- const LABEL_H = 16;
37401
- const SIMILARITY_NEIGHBORS = 5;
37402
- const SIMILARITY_MIN_WEIGHT = 0.25;
37403
- const drawnPairs = /* @__PURE__ */ new Set();
37404
- for (const edge of propEdges) drawnPairs.add(edgeKeyOf(edge.source, edge.target));
37405
- const similarityNeighbors = /* @__PURE__ */ new Map();
37406
- {
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;
37408
+ const drawnPairs = /* @__PURE__ */ new Set();
37409
+ for (const edge of propEdges) drawnPairs.add(edgeKeyOf(edge.source, edge.target));
37410
+ const similarityNeighbors = /* @__PURE__ */ new Map();
37407
37411
  const bySource = /* @__PURE__ */ new Map();
37408
37412
  for (const pair of propSimilarity) {
37409
37413
  if (pair.weight < SIMILARITY_MIN_WEIGHT) continue;
@@ -37414,113 +37418,33 @@ var init_GraphCanvas = __esm({
37414
37418
  }
37415
37419
  for (const [id, arr] of bySource) {
37416
37420
  arr.sort((a, b) => b.weight - a.weight);
37417
- const keep = new Set(arr.slice(0, SIMILARITY_NEIGHBORS).map((p) => p.target));
37418
- similarityNeighbors.set(id, keep);
37419
- }
37420
- }
37421
- const activeSimilarity = propSimilarity.filter((pair) => {
37422
- if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) return false;
37423
- const a = similarityNeighbors.get(pair.source);
37424
- const b = similarityNeighbors.get(pair.target);
37425
- return (a?.has(pair.target) ?? false) || (b?.has(pair.source) ?? false);
37426
- });
37427
- const tick = (iter, options) => {
37428
- const skipForces = options?.skipForces ?? false;
37429
- const nodes = nodesRef.current;
37430
- const centerX = w / 2;
37431
- const centerY = h / 2;
37432
- const temp = skipForces ? 0 : Math.max(COOL, 1 - iter / maxIterations);
37433
- for (const node of nodes) {
37434
- node.fx = 0;
37435
- node.fy = 0;
37436
- }
37437
- if (!skipForces) {
37438
- for (let i = 0; i < nodes.length; i++) {
37439
- for (let j = i + 1; j < nodes.length; j++) {
37440
- const dx = nodes[j].x - nodes[i].x;
37441
- const dy = nodes[j].y - nodes[i].y;
37442
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37443
- const force = repulsion / (dist * dist) * temp;
37444
- const fx = dx / dist * force;
37445
- const fy = dy / dist * force;
37446
- nodes[i].fx -= fx;
37447
- nodes[i].fy -= fy;
37448
- nodes[j].fx += fx;
37449
- nodes[j].fy += fy;
37450
- }
37451
- }
37452
- const nodeById = new Map(nodes.map((n) => [n.id, n]));
37453
- const spring = (a, b, rest, k) => {
37454
- const dx = b.x - a.x;
37455
- const dy = b.y - a.y;
37456
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37457
- const force = (dist - rest) * k * temp;
37458
- const fx = dx / dist * force;
37459
- const fy = dy / dist * force;
37460
- a.fx += fx;
37461
- a.fy += fy;
37462
- b.fx -= fx;
37463
- b.fy -= fy;
37464
- };
37465
- for (const edge of propEdges) {
37466
- const source = nodeById.get(edge.source);
37467
- const target = nodeById.get(edge.target);
37468
- if (!source || !target) continue;
37469
- drawnPairs.add(edgeKeyOf(edge.source, edge.target));
37470
- const w2 = Math.min(1, Math.max(0, edge.weight ?? 1));
37471
- spring(source, target, linkDistance * (0.35 + (1 - w2) * 0.3), 0.14);
37472
- }
37473
- for (const pair of activeSimilarity) {
37474
- const source = nodeById.get(pair.source);
37475
- const target = nodeById.get(pair.target);
37476
- if (!source || !target) continue;
37477
- const w2 = Math.min(1, Math.max(0, pair.weight));
37478
- spring(source, target, linkDistance * (1.35 - 0.55 * w2), 0.015);
37479
- }
37480
- const centroids = /* @__PURE__ */ new Map();
37481
- for (const node of nodes) {
37482
- const g = node.group ?? "__none__";
37483
- let c = centroids.get(g);
37484
- if (!c) {
37485
- c = { x: 0, y: 0, n: 0 };
37486
- centroids.set(g, c);
37487
- }
37488
- c.x += node.x;
37489
- c.y += node.y;
37490
- c.n += 1;
37491
- }
37492
- const multiCluster = centroids.size > 1;
37493
- for (const node of nodes) {
37494
- if (multiCluster) {
37495
- const c = centroids.get(node.group ?? "__none__");
37496
- if (c && c.n > 1) {
37497
- node.fx += (c.x / c.n - node.x) * 0.06 * temp;
37498
- node.fy += (c.y / c.n - node.y) * 0.06 * temp;
37499
- } else {
37500
- node.fx += (centerX - node.x) * 0.01 * temp;
37501
- node.fy += (centerY - node.y) * 0.01 * temp;
37502
- }
37503
- } else {
37504
- node.fx += (centerX - node.x) * 8e-3 * temp;
37505
- node.fy += (centerY - node.y) * 8e-3 * temp;
37506
- }
37507
- }
37508
- const damping = 0.9;
37509
- for (const node of nodes) {
37510
- node.vx = (node.vx + node.fx) * damping;
37511
- node.vy = (node.vy + node.fy) * damping;
37512
- node.x += node.vx;
37513
- node.y += node.vy;
37514
- }
37421
+ similarityNeighbors.set(id, new Set(arr.slice(0, SIMILARITY_NEIGHBORS).map((p) => p.target)));
37515
37422
  }
37516
- for (const node of nodes) {
37517
- node.x = Math.max(30, Math.min(w - 30, node.x));
37518
- 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
+ });
37519
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();
37520
37444
  const pad = nodeSpacing / 2;
37521
37445
  const rectsOf = (n) => {
37522
37446
  const r2 = n.size || 8;
37523
- const lw = showLabels ? n.labelW ?? (n.labelW = n.label ? measureLabelWidth(n.label, labelFont) : 0) : 0;
37447
+ const lw = showLabels ? n.labelW ?? 0 : 0;
37524
37448
  return {
37525
37449
  circle: [n.x - r2 - pad, n.y - r2 - pad, n.x + r2 + pad, n.y + r2 + pad],
37526
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]
@@ -37532,11 +37456,12 @@ var init_GraphCanvas = __esm({
37532
37456
  if (ox <= 0 || oy <= 0) return null;
37533
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 };
37534
37458
  };
37535
- for (let pass = 0; pass < COLLIDE_PASSES; pass++) {
37536
- for (let i = 0; i < nodes.length; i++) {
37537
- for (let j = i + 1; j < nodes.length; j++) {
37538
- const a = nodes[i];
37539
- 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];
37540
37465
  const ra = rectsOf(a);
37541
37466
  const rb = rectsOf(b);
37542
37467
  let best = null;
@@ -37545,31 +37470,25 @@ var init_GraphCanvas = __esm({
37545
37470
  if (s && (!best || s.depth < best.depth)) best = s;
37546
37471
  }
37547
37472
  if (best) {
37473
+ moved = true;
37548
37474
  const push = best.depth / 2;
37549
37475
  if (best.axis === "x") {
37550
37476
  a.x = Math.max(30, Math.min(w - 30, a.x - push * best.sign));
37551
37477
  b.x = Math.max(30, Math.min(w - 30, b.x + push * best.sign));
37552
- a.vx = 0;
37553
- b.vx = 0;
37554
37478
  } else {
37555
37479
  a.y = Math.max(30, Math.min(h - 30, a.y - push * best.sign));
37556
37480
  b.y = Math.max(30, Math.min(h - 30, b.y + push * best.sign));
37557
- a.vy = 0;
37558
- b.vy = 0;
37559
37481
  }
37560
37482
  }
37561
37483
  }
37562
37484
  }
37485
+ if (!moved) break;
37563
37486
  }
37564
- };
37565
- if (fullRelayout) {
37566
- for (let i = 0; i < maxIterations; i++) tick(i);
37567
- for (let i = 0; i < 4; i++) tick(maxIterations, { skipForces: true });
37568
37487
  const fitPad = 40;
37569
37488
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
37570
37489
  for (const node of nodesRef.current) {
37571
37490
  const r2 = node.size || 8;
37572
- const lw = showLabels ? node.labelW ?? (node.labelW = node.label ? measureLabelWidth(node.label, labelFont) : 0) : 0;
37491
+ const lw = showLabels ? node.labelW ?? 0 : 0;
37573
37492
  minX = Math.min(minX, node.x - r2, node.x - lw / 2);
37574
37493
  minY = Math.min(minY, node.y - r2);
37575
37494
  maxX = Math.max(maxX, node.x + r2, node.x + lw / 2);
@@ -37690,6 +37609,7 @@ var init_GraphCanvas = __esm({
37690
37609
  }
37691
37610
  ctx.stroke();
37692
37611
  if (showLabels && node.label) {
37612
+ const displayLabel = truncateLabel(node.label);
37693
37613
  ctx.font = `${isSelected || isHovered ? "600" : "500"} 12px ${fontFamily}`;
37694
37614
  ctx.textAlign = "center";
37695
37615
  ctx.textBaseline = "middle";
@@ -37697,9 +37617,9 @@ var init_GraphCanvas = __esm({
37697
37617
  ctx.lineWidth = 3;
37698
37618
  ctx.lineJoin = "round";
37699
37619
  ctx.strokeStyle = bgColor;
37700
- ctx.strokeText(node.label, node.x, ly);
37620
+ ctx.strokeText(displayLabel, node.x, ly);
37701
37621
  ctx.fillStyle = isSelected || isHovered ? fgColor : mutedColor;
37702
- ctx.fillText(node.label, node.x, ly);
37622
+ ctx.fillText(displayLabel, node.x, ly);
37703
37623
  }
37704
37624
  if (node.badge && node.badge > 1) {
37705
37625
  const bx = node.x + radius * 0.7;
@@ -37847,6 +37767,20 @@ var init_GraphCanvas = __esm({
37847
37767
  },
37848
37768
  [toCoords, nodeAt, onNodeDoubleClick]
37849
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
+ })();
37850
37784
  if (isLoading) {
37851
37785
  return /* @__PURE__ */ jsx(LoadingState, { message: t("common.loading"), className });
37852
37786
  }
@@ -37900,23 +37834,43 @@ var init_GraphCanvas = __esm({
37900
37834
  ]
37901
37835
  }
37902
37836
  ),
37903
- /* @__PURE__ */ jsx(Box, { className: "w-full bg-background", children: /* @__PURE__ */ jsx(
37904
- "canvas",
37905
- {
37906
- ref: canvasRef,
37907
- width: Math.round(logicalW * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
37908
- height: Math.round(height * (typeof window !== "undefined" && window.devicePixelRatio || 1)),
37909
- className: "w-full cursor-grab active:cursor-grabbing touch-none",
37910
- style: { height },
37911
- onPointerDown: gestureHandlers.onPointerDown,
37912
- onPointerMove: gestureHandlers.onPointerMove,
37913
- onPointerUp: gestureHandlers.onPointerUp,
37914
- onPointerCancel: gestureHandlers.onPointerCancel,
37915
- onPointerLeave: handlePointerLeave,
37916
- onWheel: gestureHandlers.onWheel,
37917
- onDoubleClick: handleDoubleClick
37918
- }
37919
- ) }),
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
+ ] }),
37920
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: [
37921
37875
  /* @__PURE__ */ jsx(
37922
37876
  Box,