@almadar/ui 5.118.0 → 5.120.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.
@@ -35367,11 +35367,11 @@ var init_DocumentViewer = __esm({
35367
35367
  DocumentViewer.displayName = "DocumentViewer";
35368
35368
  }
35369
35369
  });
35370
- function measureLabelWidth(text) {
35370
+ function measureLabelWidth(text, fontFamily = "system-ui") {
35371
35371
  if (typeof document === "undefined") return text.length * 6;
35372
35372
  if (!labelMeasureCtx) labelMeasureCtx = document.createElement("canvas").getContext("2d");
35373
35373
  if (!labelMeasureCtx) return text.length * 6;
35374
- labelMeasureCtx.font = "12px system-ui";
35374
+ labelMeasureCtx.font = `12px ${fontFamily}`;
35375
35375
  return labelMeasureCtx.measureText(text).width + 4;
35376
35376
  }
35377
35377
  function getGroupColor(group, groups) {
@@ -35396,6 +35396,9 @@ function mulberry32(a) {
35396
35396
  return ((t ^ t >>> 14) >>> 0) / 4294967296;
35397
35397
  };
35398
35398
  }
35399
+ function edgeKeyOf(a, b) {
35400
+ return a < b ? `${a}\0${b}` : `${b}\0${a}`;
35401
+ }
35399
35402
  function resolveColor3(color, el) {
35400
35403
  const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color.trim());
35401
35404
  if (!m) return color;
@@ -35527,8 +35530,12 @@ var init_GraphCanvas = __esm({
35527
35530
  React83.useEffect(() => {
35528
35531
  const canvas = canvasRef.current;
35529
35532
  if (!canvas || propNodes.length === 0) return;
35530
- const w = logicalW;
35531
- const h = height;
35533
+ const viewW = logicalW;
35534
+ const viewH = height;
35535
+ const layoutScale = Math.max(1, Math.min(3, Math.sqrt(propNodes.length / 12)));
35536
+ const w = viewW * layoutScale;
35537
+ const h = viewH * layoutScale;
35538
+ const labelFont = resolveColor3("var(--font-family)", canvas) || "system-ui";
35532
35539
  const prevPos = /* @__PURE__ */ new Map();
35533
35540
  for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
35534
35541
  const simNodes = propNodes.map((n, idx) => {
@@ -35563,133 +35570,192 @@ var init_GraphCanvas = __esm({
35563
35570
  const fullRelayout = !laidOutRef.current || overlap < 0.5;
35564
35571
  if (layout === "force") {
35565
35572
  const maxIterations = 300;
35566
- const tick = () => {
35573
+ const COOL = 0.12;
35574
+ const COLLIDE_PASSES = 6;
35575
+ const LABEL_GAP = 12;
35576
+ const LABEL_H = 16;
35577
+ const SIMILARITY_NEIGHBORS = 5;
35578
+ const SIMILARITY_MIN_WEIGHT = 0.25;
35579
+ const drawnPairs = /* @__PURE__ */ new Set();
35580
+ for (const edge of propEdges) drawnPairs.add(edgeKeyOf(edge.source, edge.target));
35581
+ const similarityNeighbors = /* @__PURE__ */ new Map();
35582
+ {
35583
+ const bySource = /* @__PURE__ */ new Map();
35584
+ for (const pair of propSimilarity) {
35585
+ if (pair.weight < SIMILARITY_MIN_WEIGHT) continue;
35586
+ if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) continue;
35587
+ const arr = bySource.get(pair.source) ?? [];
35588
+ arr.push(pair);
35589
+ bySource.set(pair.source, arr);
35590
+ }
35591
+ for (const [id, arr] of bySource) {
35592
+ arr.sort((a, b) => b.weight - a.weight);
35593
+ const keep = new Set(arr.slice(0, SIMILARITY_NEIGHBORS).map((p) => p.target));
35594
+ similarityNeighbors.set(id, keep);
35595
+ }
35596
+ }
35597
+ const activeSimilarity = propSimilarity.filter((pair) => {
35598
+ if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) return false;
35599
+ const a = similarityNeighbors.get(pair.source);
35600
+ const b = similarityNeighbors.get(pair.target);
35601
+ return (a?.has(pair.target) ?? false) || (b?.has(pair.source) ?? false);
35602
+ });
35603
+ const tick = (iter, options) => {
35604
+ const skipForces = options?.skipForces ?? false;
35567
35605
  const nodes = nodesRef.current;
35568
35606
  const centerX = w / 2;
35569
35607
  const centerY = h / 2;
35608
+ const temp = skipForces ? 0 : Math.max(COOL, 1 - iter / maxIterations);
35570
35609
  for (const node of nodes) {
35571
35610
  node.fx = 0;
35572
35611
  node.fy = 0;
35573
35612
  }
35574
- for (let i = 0; i < nodes.length; i++) {
35575
- for (let j = i + 1; j < nodes.length; j++) {
35576
- const dx = nodes[j].x - nodes[i].x;
35577
- const dy = nodes[j].y - nodes[i].y;
35578
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
35579
- const force = repulsion / (dist * dist);
35580
- const fx = dx / dist * force;
35581
- const fy = dy / dist * force;
35582
- nodes[i].fx -= fx;
35583
- nodes[i].fy -= fy;
35584
- nodes[j].fx += fx;
35585
- nodes[j].fy += fy;
35613
+ if (!skipForces) {
35614
+ for (let i = 0; i < nodes.length; i++) {
35615
+ for (let j = i + 1; j < nodes.length; j++) {
35616
+ const dx = nodes[j].x - nodes[i].x;
35617
+ const dy = nodes[j].y - nodes[i].y;
35618
+ const dist = Math.sqrt(dx * dx + dy * dy) || 1;
35619
+ const force = repulsion / (dist * dist) * temp;
35620
+ const fx = dx / dist * force;
35621
+ const fy = dy / dist * force;
35622
+ nodes[i].fx -= fx;
35623
+ nodes[i].fy -= fy;
35624
+ nodes[j].fx += fx;
35625
+ nodes[j].fy += fy;
35626
+ }
35586
35627
  }
35587
- }
35588
- const nodeById = new Map(nodes.map((n) => [n.id, n]));
35589
- if (propSimilarity.length > 0) {
35590
- for (const pair of propSimilarity) {
35591
- const source = nodeById.get(pair.source);
35592
- const target = nodeById.get(pair.target);
35593
- if (!source || !target) continue;
35594
- const dx = target.x - source.x;
35595
- const dy = target.y - source.y;
35628
+ const nodeById = new Map(nodes.map((n) => [n.id, n]));
35629
+ const spring = (a, b, rest, k) => {
35630
+ const dx = b.x - a.x;
35631
+ const dy = b.y - a.y;
35596
35632
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
35597
- const w2 = Math.min(1, Math.max(0, pair.weight));
35598
- const linkTarget = linkDistance * (1 - w2);
35599
- const force = (dist - linkTarget) * (0.04 + 0.1 * w2);
35633
+ const force = (dist - rest) * k * temp;
35600
35634
  const fx = dx / dist * force;
35601
35635
  const fy = dy / dist * force;
35602
- source.fx += fx;
35603
- source.fy += fy;
35604
- target.fx -= fx;
35605
- target.fy -= fy;
35606
- }
35607
- } else {
35636
+ a.fx += fx;
35637
+ a.fy += fy;
35638
+ b.fx -= fx;
35639
+ b.fy -= fy;
35640
+ };
35608
35641
  for (const edge of propEdges) {
35609
35642
  const source = nodeById.get(edge.source);
35610
35643
  const target = nodeById.get(edge.target);
35611
35644
  if (!source || !target) continue;
35612
- const dx = target.x - source.x;
35613
- const dy = target.y - source.y;
35614
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
35645
+ drawnPairs.add(edgeKeyOf(edge.source, edge.target));
35615
35646
  const w2 = Math.min(1, Math.max(0, edge.weight ?? 1));
35616
- const linkTarget = linkDistance * (0.5 + (1 - w2) * 1.5);
35617
- const force = (dist - linkTarget) * (0.04 + 0.1 * w2);
35618
- const fx = dx / dist * force;
35619
- const fy = dy / dist * force;
35620
- source.fx += fx;
35621
- source.fy += fy;
35622
- target.fx -= fx;
35623
- target.fy -= fy;
35647
+ spring(source, target, linkDistance * (0.35 + (1 - w2) * 0.3), 0.14);
35624
35648
  }
35625
- }
35626
- const centroids = /* @__PURE__ */ new Map();
35627
- for (const node of nodes) {
35628
- const g = node.group ?? "__none__";
35629
- let c = centroids.get(g);
35630
- if (!c) {
35631
- c = { x: 0, y: 0, n: 0 };
35632
- centroids.set(g, c);
35649
+ for (const pair of activeSimilarity) {
35650
+ const source = nodeById.get(pair.source);
35651
+ const target = nodeById.get(pair.target);
35652
+ if (!source || !target) continue;
35653
+ const w2 = Math.min(1, Math.max(0, pair.weight));
35654
+ spring(source, target, linkDistance * (1.35 - 0.55 * w2), 0.015);
35633
35655
  }
35634
- c.x += node.x;
35635
- c.y += node.y;
35636
- c.n += 1;
35637
- }
35638
- for (const node of nodes) {
35639
- const c = centroids.get(node.group ?? "__none__");
35640
- if (c && c.n > 1) {
35641
- node.fx += (c.x / c.n - node.x) * 0.04;
35642
- node.fy += (c.y / c.n - node.y) * 0.04;
35643
- } else {
35644
- node.fx += (centerX - node.x) * 0.01;
35645
- node.fy += (centerY - node.y) * 0.01;
35656
+ const centroids = /* @__PURE__ */ new Map();
35657
+ for (const node of nodes) {
35658
+ const g = node.group ?? "__none__";
35659
+ let c = centroids.get(g);
35660
+ if (!c) {
35661
+ c = { x: 0, y: 0, n: 0 };
35662
+ centroids.set(g, c);
35663
+ }
35664
+ c.x += node.x;
35665
+ c.y += node.y;
35666
+ c.n += 1;
35667
+ }
35668
+ const multiCluster = centroids.size > 1;
35669
+ for (const node of nodes) {
35670
+ if (multiCluster) {
35671
+ const c = centroids.get(node.group ?? "__none__");
35672
+ if (c && c.n > 1) {
35673
+ node.fx += (c.x / c.n - node.x) * 0.06 * temp;
35674
+ node.fy += (c.y / c.n - node.y) * 0.06 * temp;
35675
+ } else {
35676
+ node.fx += (centerX - node.x) * 0.01 * temp;
35677
+ node.fy += (centerY - node.y) * 0.01 * temp;
35678
+ }
35679
+ } else {
35680
+ node.fx += (centerX - node.x) * 8e-3 * temp;
35681
+ node.fy += (centerY - node.y) * 8e-3 * temp;
35682
+ }
35683
+ }
35684
+ const damping = 0.9;
35685
+ for (const node of nodes) {
35686
+ node.vx = (node.vx + node.fx) * damping;
35687
+ node.vy = (node.vy + node.fy) * damping;
35688
+ node.x += node.vx;
35689
+ node.y += node.vy;
35646
35690
  }
35647
35691
  }
35648
- const damping = 0.9;
35649
35692
  for (const node of nodes) {
35650
- node.vx = (node.vx + node.fx) * damping;
35651
- node.vy = (node.vy + node.fy) * damping;
35652
- node.x += node.vx;
35653
- node.y += node.vy;
35654
35693
  node.x = Math.max(30, Math.min(w - 30, node.x));
35655
35694
  node.y = Math.max(30, Math.min(h - 30, node.y));
35656
35695
  }
35657
- const LABEL_GAP = 12;
35658
- const LABEL_H = 16;
35659
35696
  const pad = nodeSpacing / 2;
35660
- for (let i = 0; i < nodes.length; i++) {
35661
- for (let j = i + 1; j < nodes.length; j++) {
35662
- const a = nodes[i];
35663
- const b = nodes[j];
35664
- const ar = a.size || 8;
35665
- const br = b.size || 8;
35666
- if (showLabels) {
35667
- if (a.labelW === void 0) a.labelW = a.label ? measureLabelWidth(a.label) : 0;
35668
- if (b.labelW === void 0) b.labelW = b.label ? measureLabelWidth(b.label) : 0;
35669
- }
35670
- const labelBelow = showLabels ? LABEL_GAP + LABEL_H : 0;
35671
- const aHalfW = Math.max(ar, (a.labelW ?? 0) / 2) + pad;
35672
- const bHalfW = Math.max(br, (b.labelW ?? 0) / 2) + pad;
35673
- const aTop = a.y - ar - pad, aBot = a.y + ar + labelBelow + pad;
35674
- const bTop = b.y - br - pad, bBot = b.y + br + labelBelow + pad;
35675
- const overlapX = Math.min(a.x + aHalfW, b.x + bHalfW) - Math.max(a.x - aHalfW, b.x - bHalfW);
35676
- const overlapY = Math.min(aBot, bBot) - Math.max(aTop, bTop);
35677
- if (overlapX > 0 && overlapY > 0) {
35678
- if (overlapX <= overlapY) {
35679
- const push = overlapX / 2 * (b.x >= a.x ? 1 : -1);
35680
- a.x = Math.max(30, Math.min(w - 30, a.x - push));
35681
- b.x = Math.max(30, Math.min(w - 30, b.x + push));
35682
- } else {
35683
- const push = overlapY / 2 * (bTop + bBot >= aTop + aBot ? 1 : -1);
35684
- a.y = Math.max(30, Math.min(h - 30, a.y - push));
35685
- b.y = Math.max(30, Math.min(h - 30, b.y + push));
35697
+ const rectsOf = (n) => {
35698
+ const r = n.size || 8;
35699
+ const lw = showLabels ? n.labelW ?? (n.labelW = n.label ? measureLabelWidth(n.label, labelFont) : 0) : 0;
35700
+ return {
35701
+ circle: [n.x - r - pad, n.y - r - pad, n.x + r + pad, n.y + r + pad],
35702
+ label: [n.x - lw / 2 - pad, n.y + r + LABEL_GAP - 8, n.x + lw / 2 + pad, n.y + r + LABEL_GAP + LABEL_H]
35703
+ };
35704
+ };
35705
+ const sep = (r1, r2) => {
35706
+ const ox = Math.min(r1[2], r2[2]) - Math.max(r1[0], r2[0]);
35707
+ const oy = Math.min(r1[3], r2[3]) - Math.max(r1[1], r2[1]);
35708
+ if (ox <= 0 || oy <= 0) return null;
35709
+ 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 };
35710
+ };
35711
+ for (let pass = 0; pass < COLLIDE_PASSES; pass++) {
35712
+ for (let i = 0; i < nodes.length; i++) {
35713
+ for (let j = i + 1; j < nodes.length; j++) {
35714
+ const a = nodes[i];
35715
+ const b = nodes[j];
35716
+ const ra = rectsOf(a);
35717
+ const rb = rectsOf(b);
35718
+ let best = null;
35719
+ for (const [r1, r2] of [[ra.circle, rb.circle], [ra.circle, rb.label], [ra.label, rb.circle], [ra.label, rb.label]]) {
35720
+ const s = sep(r1, r2);
35721
+ if (s && (!best || s.depth < best.depth)) best = s;
35722
+ }
35723
+ if (best) {
35724
+ const push = best.depth / 2;
35725
+ if (best.axis === "x") {
35726
+ a.x = Math.max(30, Math.min(w - 30, a.x - push * best.sign));
35727
+ b.x = Math.max(30, Math.min(w - 30, b.x + push * best.sign));
35728
+ a.vx = 0;
35729
+ b.vx = 0;
35730
+ } else {
35731
+ a.y = Math.max(30, Math.min(h - 30, a.y - push * best.sign));
35732
+ b.y = Math.max(30, Math.min(h - 30, b.y + push * best.sign));
35733
+ a.vy = 0;
35734
+ b.vy = 0;
35735
+ }
35686
35736
  }
35687
35737
  }
35688
35738
  }
35689
35739
  }
35690
35740
  };
35691
35741
  if (fullRelayout) {
35692
- for (let i = 0; i < maxIterations; i++) tick();
35742
+ for (let i = 0; i < maxIterations; i++) tick(i);
35743
+ for (let i = 0; i < 4; i++) tick(maxIterations, { skipForces: true });
35744
+ const fitPad = 40;
35745
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
35746
+ for (const node of nodesRef.current) {
35747
+ const r = node.size || 8;
35748
+ const lw = showLabels ? node.labelW ?? (node.labelW = node.label ? measureLabelWidth(node.label, labelFont) : 0) : 0;
35749
+ minX = Math.min(minX, node.x - r, node.x - lw / 2);
35750
+ minY = Math.min(minY, node.y - r);
35751
+ maxX = Math.max(maxX, node.x + r, node.x + lw / 2);
35752
+ maxY = Math.max(maxY, node.y + r + LABEL_GAP + LABEL_H);
35753
+ }
35754
+ const bboxW = Math.max(1, maxX - minX + fitPad * 2);
35755
+ const bboxH = Math.max(1, maxY - minY + fitPad * 2);
35756
+ const nextZoom = Math.min(1, viewW / bboxW, viewH / bboxH);
35757
+ setZoom(nextZoom);
35758
+ setOffset({ x: fitPad - minX * nextZoom, y: fitPad - minY * nextZoom });
35693
35759
  laidOutRef.current = true;
35694
35760
  } else {
35695
35761
  const centroids = /* @__PURE__ */ new Map();