@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.
@@ -34688,11 +34688,11 @@ var init_DocumentViewer = __esm({
34688
34688
  DocumentViewer.displayName = "DocumentViewer";
34689
34689
  }
34690
34690
  });
34691
- function measureLabelWidth(text) {
34691
+ function measureLabelWidth(text, fontFamily = "system-ui") {
34692
34692
  if (typeof document === "undefined") return text.length * 6;
34693
34693
  if (!labelMeasureCtx) labelMeasureCtx = document.createElement("canvas").getContext("2d");
34694
34694
  if (!labelMeasureCtx) return text.length * 6;
34695
- labelMeasureCtx.font = "12px system-ui";
34695
+ labelMeasureCtx.font = `12px ${fontFamily}`;
34696
34696
  return labelMeasureCtx.measureText(text).width + 4;
34697
34697
  }
34698
34698
  function getGroupColor(group, groups) {
@@ -34717,6 +34717,9 @@ function mulberry32(a) {
34717
34717
  return ((t ^ t >>> 14) >>> 0) / 4294967296;
34718
34718
  };
34719
34719
  }
34720
+ function edgeKeyOf(a, b) {
34721
+ return a < b ? `${a}\0${b}` : `${b}\0${a}`;
34722
+ }
34720
34723
  function resolveColor3(color, el) {
34721
34724
  const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color.trim());
34722
34725
  if (!m) return color;
@@ -34848,8 +34851,12 @@ var init_GraphCanvas = __esm({
34848
34851
  useEffect(() => {
34849
34852
  const canvas = canvasRef.current;
34850
34853
  if (!canvas || propNodes.length === 0) return;
34851
- const w = logicalW;
34852
- const h = height;
34854
+ const viewW = logicalW;
34855
+ const viewH = height;
34856
+ const layoutScale = Math.max(1, Math.min(3, Math.sqrt(propNodes.length / 12)));
34857
+ const w = viewW * layoutScale;
34858
+ const h = viewH * layoutScale;
34859
+ const labelFont = resolveColor3("var(--font-family)", canvas) || "system-ui";
34853
34860
  const prevPos = /* @__PURE__ */ new Map();
34854
34861
  for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
34855
34862
  const simNodes = propNodes.map((n, idx) => {
@@ -34884,133 +34891,192 @@ var init_GraphCanvas = __esm({
34884
34891
  const fullRelayout = !laidOutRef.current || overlap < 0.5;
34885
34892
  if (layout === "force") {
34886
34893
  const maxIterations = 300;
34887
- const tick = () => {
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
+ {
34904
+ const bySource = /* @__PURE__ */ new Map();
34905
+ for (const pair of propSimilarity) {
34906
+ if (pair.weight < SIMILARITY_MIN_WEIGHT) continue;
34907
+ if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) continue;
34908
+ const arr = bySource.get(pair.source) ?? [];
34909
+ arr.push(pair);
34910
+ bySource.set(pair.source, arr);
34911
+ }
34912
+ for (const [id, arr] of bySource) {
34913
+ 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);
34916
+ }
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;
34888
34926
  const nodes = nodesRef.current;
34889
34927
  const centerX = w / 2;
34890
34928
  const centerY = h / 2;
34929
+ const temp = skipForces ? 0 : Math.max(COOL, 1 - iter / maxIterations);
34891
34930
  for (const node of nodes) {
34892
34931
  node.fx = 0;
34893
34932
  node.fy = 0;
34894
34933
  }
34895
- for (let i = 0; i < nodes.length; i++) {
34896
- for (let j = i + 1; j < nodes.length; j++) {
34897
- const dx = nodes[j].x - nodes[i].x;
34898
- const dy = nodes[j].y - nodes[i].y;
34899
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
34900
- const force = repulsion / (dist * dist);
34901
- const fx = dx / dist * force;
34902
- const fy = dy / dist * force;
34903
- nodes[i].fx -= fx;
34904
- nodes[i].fy -= fy;
34905
- nodes[j].fx += fx;
34906
- nodes[j].fy += fy;
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
+ }
34907
34948
  }
34908
- }
34909
- const nodeById = new Map(nodes.map((n) => [n.id, n]));
34910
- if (propSimilarity.length > 0) {
34911
- for (const pair of propSimilarity) {
34912
- const source = nodeById.get(pair.source);
34913
- const target = nodeById.get(pair.target);
34914
- if (!source || !target) continue;
34915
- const dx = target.x - source.x;
34916
- const dy = target.y - source.y;
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;
34917
34953
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
34918
- const w2 = Math.min(1, Math.max(0, pair.weight));
34919
- const linkTarget = linkDistance * (1 - w2);
34920
- const force = (dist - linkTarget) * (0.04 + 0.1 * w2);
34954
+ const force = (dist - rest) * k * temp;
34921
34955
  const fx = dx / dist * force;
34922
34956
  const fy = dy / dist * force;
34923
- source.fx += fx;
34924
- source.fy += fy;
34925
- target.fx -= fx;
34926
- target.fy -= fy;
34927
- }
34928
- } else {
34957
+ a.fx += fx;
34958
+ a.fy += fy;
34959
+ b.fx -= fx;
34960
+ b.fy -= fy;
34961
+ };
34929
34962
  for (const edge of propEdges) {
34930
34963
  const source = nodeById.get(edge.source);
34931
34964
  const target = nodeById.get(edge.target);
34932
34965
  if (!source || !target) continue;
34933
- const dx = target.x - source.x;
34934
- const dy = target.y - source.y;
34935
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
34966
+ drawnPairs.add(edgeKeyOf(edge.source, edge.target));
34936
34967
  const w2 = Math.min(1, Math.max(0, edge.weight ?? 1));
34937
- const linkTarget = linkDistance * (0.5 + (1 - w2) * 1.5);
34938
- const force = (dist - linkTarget) * (0.04 + 0.1 * w2);
34939
- const fx = dx / dist * force;
34940
- const fy = dy / dist * force;
34941
- source.fx += fx;
34942
- source.fy += fy;
34943
- target.fx -= fx;
34944
- target.fy -= fy;
34968
+ spring(source, target, linkDistance * (0.35 + (1 - w2) * 0.3), 0.14);
34945
34969
  }
34946
- }
34947
- const centroids = /* @__PURE__ */ new Map();
34948
- for (const node of nodes) {
34949
- const g = node.group ?? "__none__";
34950
- let c = centroids.get(g);
34951
- if (!c) {
34952
- c = { x: 0, y: 0, n: 0 };
34953
- centroids.set(g, c);
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);
34954
34976
  }
34955
- c.x += node.x;
34956
- c.y += node.y;
34957
- c.n += 1;
34958
- }
34959
- for (const node of nodes) {
34960
- const c = centroids.get(node.group ?? "__none__");
34961
- if (c && c.n > 1) {
34962
- node.fx += (c.x / c.n - node.x) * 0.04;
34963
- node.fy += (c.y / c.n - node.y) * 0.04;
34964
- } else {
34965
- node.fx += (centerX - node.x) * 0.01;
34966
- node.fy += (centerY - node.y) * 0.01;
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;
34967
35011
  }
34968
35012
  }
34969
- const damping = 0.9;
34970
35013
  for (const node of nodes) {
34971
- node.vx = (node.vx + node.fx) * damping;
34972
- node.vy = (node.vy + node.fy) * damping;
34973
- node.x += node.vx;
34974
- node.y += node.vy;
34975
35014
  node.x = Math.max(30, Math.min(w - 30, node.x));
34976
35015
  node.y = Math.max(30, Math.min(h - 30, node.y));
34977
35016
  }
34978
- const LABEL_GAP = 12;
34979
- const LABEL_H = 16;
34980
35017
  const pad = nodeSpacing / 2;
34981
- for (let i = 0; i < nodes.length; i++) {
34982
- for (let j = i + 1; j < nodes.length; j++) {
34983
- const a = nodes[i];
34984
- const b = nodes[j];
34985
- const ar = a.size || 8;
34986
- const br = b.size || 8;
34987
- if (showLabels) {
34988
- if (a.labelW === void 0) a.labelW = a.label ? measureLabelWidth(a.label) : 0;
34989
- if (b.labelW === void 0) b.labelW = b.label ? measureLabelWidth(b.label) : 0;
34990
- }
34991
- const labelBelow = showLabels ? LABEL_GAP + LABEL_H : 0;
34992
- const aHalfW = Math.max(ar, (a.labelW ?? 0) / 2) + pad;
34993
- const bHalfW = Math.max(br, (b.labelW ?? 0) / 2) + pad;
34994
- const aTop = a.y - ar - pad, aBot = a.y + ar + labelBelow + pad;
34995
- const bTop = b.y - br - pad, bBot = b.y + br + labelBelow + pad;
34996
- const overlapX = Math.min(a.x + aHalfW, b.x + bHalfW) - Math.max(a.x - aHalfW, b.x - bHalfW);
34997
- const overlapY = Math.min(aBot, bBot) - Math.max(aTop, bTop);
34998
- if (overlapX > 0 && overlapY > 0) {
34999
- if (overlapX <= overlapY) {
35000
- const push = overlapX / 2 * (b.x >= a.x ? 1 : -1);
35001
- a.x = Math.max(30, Math.min(w - 30, a.x - push));
35002
- b.x = Math.max(30, Math.min(w - 30, b.x + push));
35003
- } else {
35004
- const push = overlapY / 2 * (bTop + bBot >= aTop + aBot ? 1 : -1);
35005
- a.y = Math.max(30, Math.min(h - 30, a.y - push));
35006
- b.y = Math.max(30, Math.min(h - 30, b.y + push));
35018
+ const rectsOf = (n) => {
35019
+ const r = n.size || 8;
35020
+ const lw = showLabels ? n.labelW ?? (n.labelW = n.label ? measureLabelWidth(n.label, labelFont) : 0) : 0;
35021
+ return {
35022
+ circle: [n.x - r - pad, n.y - r - pad, n.x + r + pad, n.y + r + pad],
35023
+ label: [n.x - lw / 2 - pad, n.y + r + LABEL_GAP - 8, n.x + lw / 2 + pad, n.y + r + LABEL_GAP + LABEL_H]
35024
+ };
35025
+ };
35026
+ const sep = (r1, r2) => {
35027
+ const ox = Math.min(r1[2], r2[2]) - Math.max(r1[0], r2[0]);
35028
+ const oy = Math.min(r1[3], r2[3]) - Math.max(r1[1], r2[1]);
35029
+ if (ox <= 0 || oy <= 0) return null;
35030
+ 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
+ };
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];
35037
+ const ra = rectsOf(a);
35038
+ const rb = rectsOf(b);
35039
+ let best = null;
35040
+ for (const [r1, r2] of [[ra.circle, rb.circle], [ra.circle, rb.label], [ra.label, rb.circle], [ra.label, rb.label]]) {
35041
+ const s = sep(r1, r2);
35042
+ if (s && (!best || s.depth < best.depth)) best = s;
35043
+ }
35044
+ if (best) {
35045
+ const push = best.depth / 2;
35046
+ if (best.axis === "x") {
35047
+ a.x = Math.max(30, Math.min(w - 30, a.x - push * best.sign));
35048
+ b.x = Math.max(30, Math.min(w - 30, b.x + push * best.sign));
35049
+ a.vx = 0;
35050
+ b.vx = 0;
35051
+ } else {
35052
+ a.y = Math.max(30, Math.min(h - 30, a.y - push * best.sign));
35053
+ b.y = Math.max(30, Math.min(h - 30, b.y + push * best.sign));
35054
+ a.vy = 0;
35055
+ b.vy = 0;
35056
+ }
35007
35057
  }
35008
35058
  }
35009
35059
  }
35010
35060
  }
35011
35061
  };
35012
35062
  if (fullRelayout) {
35013
- for (let i = 0; i < maxIterations; i++) tick();
35063
+ for (let i = 0; i < maxIterations; i++) tick(i);
35064
+ for (let i = 0; i < 4; i++) tick(maxIterations, { skipForces: true });
35065
+ const fitPad = 40;
35066
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
35067
+ for (const node of nodesRef.current) {
35068
+ const r = node.size || 8;
35069
+ const lw = showLabels ? node.labelW ?? (node.labelW = node.label ? measureLabelWidth(node.label, labelFont) : 0) : 0;
35070
+ minX = Math.min(minX, node.x - r, node.x - lw / 2);
35071
+ minY = Math.min(minY, node.y - r);
35072
+ maxX = Math.max(maxX, node.x + r, node.x + lw / 2);
35073
+ maxY = Math.max(maxY, node.y + r + LABEL_GAP + LABEL_H);
35074
+ }
35075
+ const bboxW = Math.max(1, maxX - minX + fitPad * 2);
35076
+ const bboxH = Math.max(1, maxY - minY + fitPad * 2);
35077
+ const nextZoom = Math.min(1, viewW / bboxW, viewH / bboxH);
35078
+ setZoom(nextZoom);
35079
+ setOffset({ x: fitPad - minX * nextZoom, y: fitPad - minY * nextZoom });
35014
35080
  laidOutRef.current = true;
35015
35081
  } else {
35016
35082
  const centroids = /* @__PURE__ */ new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.118.0",
3
+ "version": "5.120.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [