@almadar/ui 5.116.0 → 5.117.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.
@@ -37242,7 +37242,7 @@ function measureLabelWidth(text) {
37242
37242
  if (!labelMeasureCtx) labelMeasureCtx = document.createElement("canvas").getContext("2d");
37243
37243
  if (!labelMeasureCtx) return text.length * 6;
37244
37244
  labelMeasureCtx.font = "12px system-ui";
37245
- return labelMeasureCtx.measureText(text).width;
37245
+ return labelMeasureCtx.measureText(text).width + 4;
37246
37246
  }
37247
37247
  function getGroupColor(group, groups) {
37248
37248
  if (!group) return GROUP_COLORS2[0];
@@ -37311,6 +37311,7 @@ var init_GraphCanvas = __esm({
37311
37311
  offsetRef.current = offset;
37312
37312
  const [hoveredNode, setHoveredNode] = React90.useState(null);
37313
37313
  const nodesRef = React90.useRef([]);
37314
+ const laidOutRef = React90.useRef(false);
37314
37315
  const [, forceUpdate] = React90.useState(0);
37315
37316
  const [logicalW, setLogicalW] = React90.useState(800);
37316
37317
  React90.useEffect(() => {
@@ -37380,10 +37381,13 @@ var init_GraphCanvas = __esm({
37380
37381
  if (!canvas || propNodes.length === 0) return;
37381
37382
  const w = logicalW;
37382
37383
  const h = height;
37384
+ const prevPos = /* @__PURE__ */ new Map();
37385
+ for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
37383
37386
  const simNodes = propNodes.map((n, idx) => {
37384
- let x = n.x ?? 0;
37385
- let y = n.y ?? 0;
37386
- if (!n.x || !n.y) {
37387
+ const prev = prevPos.get(n.id);
37388
+ let x = prev?.x ?? n.x ?? 0;
37389
+ let y = prev?.y ?? n.y ?? 0;
37390
+ if (!prev && (!n.x || !n.y)) {
37387
37391
  if (layout === "circular") {
37388
37392
  const angle = idx / propNodes.length * 2 * Math.PI;
37389
37393
  const radius = Math.min(w, h) * 0.35;
@@ -37403,8 +37407,12 @@ var init_GraphCanvas = __esm({
37403
37407
  return { ...n, x, y, vx: 0, vy: 0, fx: 0, fy: 0 };
37404
37408
  });
37405
37409
  nodesRef.current = simNodes;
37410
+ const prevIds = /* @__PURE__ */ new Set([...prevPos.keys()]);
37411
+ const newIdList = simNodes.map((n) => n.id);
37412
+ const kept = newIdList.filter((id) => prevIds.has(id)).length;
37413
+ const overlap = prevIds.size === 0 ? 0 : kept / Math.max(prevIds.size, newIdList.length);
37414
+ const fullRelayout = !laidOutRef.current || overlap < 0.5;
37406
37415
  if (layout === "force") {
37407
- let iterations = 0;
37408
37416
  const maxIterations = 300;
37409
37417
  const tick = () => {
37410
37418
  const nodes = nodesRef.current;
@@ -37509,20 +37517,47 @@ var init_GraphCanvas = __esm({
37509
37517
  }
37510
37518
  }
37511
37519
  }
37512
- iterations++;
37513
- forceUpdate((n) => n + 1);
37514
- if (iterations < maxIterations) {
37515
- animRef.current = requestAnimationFrame(tick);
37516
- }
37517
37520
  };
37518
- animRef.current = requestAnimationFrame(tick);
37521
+ if (fullRelayout) {
37522
+ for (let i = 0; i < maxIterations; i++) tick();
37523
+ laidOutRef.current = true;
37524
+ } else {
37525
+ const centroids = /* @__PURE__ */ new Map();
37526
+ for (const node of simNodes) {
37527
+ if (!prevPos.has(node.id)) continue;
37528
+ const g = node.group ?? "__none__";
37529
+ let c = centroids.get(g);
37530
+ if (!c) {
37531
+ c = { x: 0, y: 0, n: 0 };
37532
+ centroids.set(g, c);
37533
+ }
37534
+ c.x += node.x;
37535
+ c.y += node.y;
37536
+ c.n += 1;
37537
+ }
37538
+ const ringCount = /* @__PURE__ */ new Map();
37539
+ for (const node of simNodes) {
37540
+ if (prevPos.has(node.id)) continue;
37541
+ const g = node.group ?? "__none__";
37542
+ const c = centroids.get(g);
37543
+ const cx = c && c.n > 0 ? c.x / c.n : w / 2;
37544
+ const cy = c && c.n > 0 ? c.y / c.n : h / 2;
37545
+ const k = ringCount.get(g) ?? 0;
37546
+ ringCount.set(g, k + 1);
37547
+ const angle = k * 2.399963;
37548
+ const r2 = 22 + k * 8;
37549
+ node.x = Math.max(30, Math.min(w - 30, cx + r2 * Math.cos(angle)));
37550
+ node.y = Math.max(30, Math.min(h - 30, cy + r2 * Math.sin(angle)));
37551
+ }
37552
+ }
37553
+ forceUpdate((n) => n + 1);
37519
37554
  } else {
37520
37555
  forceUpdate((n) => n + 1);
37521
37556
  }
37522
37557
  return () => {
37523
37558
  cancelAnimationFrame(animRef.current);
37524
37559
  };
37525
- }, [propNodes, propEdges, layout, repulsion, linkDistance, nodeSpacing, showLabels, logicalW, height]);
37560
+ }, [propNodes, propEdges, layout, repulsion, linkDistance, nodeSpacing, showLabels]);
37526
37561
  React90.useEffect(() => {
37527
37562
  const canvas = canvasRef.current;
37528
37563
  if (!canvas) return;
package/dist/avl/index.js CHANGED
@@ -37196,7 +37196,7 @@ function measureLabelWidth(text) {
37196
37196
  if (!labelMeasureCtx) labelMeasureCtx = document.createElement("canvas").getContext("2d");
37197
37197
  if (!labelMeasureCtx) return text.length * 6;
37198
37198
  labelMeasureCtx.font = "12px system-ui";
37199
- return labelMeasureCtx.measureText(text).width;
37199
+ return labelMeasureCtx.measureText(text).width + 4;
37200
37200
  }
37201
37201
  function getGroupColor(group, groups) {
37202
37202
  if (!group) return GROUP_COLORS2[0];
@@ -37265,6 +37265,7 @@ var init_GraphCanvas = __esm({
37265
37265
  offsetRef.current = offset;
37266
37266
  const [hoveredNode, setHoveredNode] = useState(null);
37267
37267
  const nodesRef = useRef([]);
37268
+ const laidOutRef = useRef(false);
37268
37269
  const [, forceUpdate] = useState(0);
37269
37270
  const [logicalW, setLogicalW] = useState(800);
37270
37271
  useEffect(() => {
@@ -37334,10 +37335,13 @@ var init_GraphCanvas = __esm({
37334
37335
  if (!canvas || propNodes.length === 0) return;
37335
37336
  const w = logicalW;
37336
37337
  const h = height;
37338
+ const prevPos = /* @__PURE__ */ new Map();
37339
+ for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
37337
37340
  const simNodes = propNodes.map((n, idx) => {
37338
- let x = n.x ?? 0;
37339
- let y = n.y ?? 0;
37340
- if (!n.x || !n.y) {
37341
+ const prev = prevPos.get(n.id);
37342
+ let x = prev?.x ?? n.x ?? 0;
37343
+ let y = prev?.y ?? n.y ?? 0;
37344
+ if (!prev && (!n.x || !n.y)) {
37341
37345
  if (layout === "circular") {
37342
37346
  const angle = idx / propNodes.length * 2 * Math.PI;
37343
37347
  const radius = Math.min(w, h) * 0.35;
@@ -37357,8 +37361,12 @@ var init_GraphCanvas = __esm({
37357
37361
  return { ...n, x, y, vx: 0, vy: 0, fx: 0, fy: 0 };
37358
37362
  });
37359
37363
  nodesRef.current = simNodes;
37364
+ const prevIds = /* @__PURE__ */ new Set([...prevPos.keys()]);
37365
+ const newIdList = simNodes.map((n) => n.id);
37366
+ const kept = newIdList.filter((id) => prevIds.has(id)).length;
37367
+ const overlap = prevIds.size === 0 ? 0 : kept / Math.max(prevIds.size, newIdList.length);
37368
+ const fullRelayout = !laidOutRef.current || overlap < 0.5;
37360
37369
  if (layout === "force") {
37361
- let iterations = 0;
37362
37370
  const maxIterations = 300;
37363
37371
  const tick = () => {
37364
37372
  const nodes = nodesRef.current;
@@ -37463,20 +37471,47 @@ var init_GraphCanvas = __esm({
37463
37471
  }
37464
37472
  }
37465
37473
  }
37466
- iterations++;
37467
- forceUpdate((n) => n + 1);
37468
- if (iterations < maxIterations) {
37469
- animRef.current = requestAnimationFrame(tick);
37470
- }
37471
37474
  };
37472
- animRef.current = requestAnimationFrame(tick);
37475
+ if (fullRelayout) {
37476
+ for (let i = 0; i < maxIterations; i++) tick();
37477
+ laidOutRef.current = true;
37478
+ } else {
37479
+ const centroids = /* @__PURE__ */ new Map();
37480
+ for (const node of simNodes) {
37481
+ if (!prevPos.has(node.id)) continue;
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 ringCount = /* @__PURE__ */ new Map();
37493
+ for (const node of simNodes) {
37494
+ if (prevPos.has(node.id)) continue;
37495
+ const g = node.group ?? "__none__";
37496
+ const c = centroids.get(g);
37497
+ const cx = c && c.n > 0 ? c.x / c.n : w / 2;
37498
+ const cy = c && c.n > 0 ? c.y / c.n : h / 2;
37499
+ const k = ringCount.get(g) ?? 0;
37500
+ ringCount.set(g, k + 1);
37501
+ const angle = k * 2.399963;
37502
+ const r2 = 22 + k * 8;
37503
+ node.x = Math.max(30, Math.min(w - 30, cx + r2 * Math.cos(angle)));
37504
+ node.y = Math.max(30, Math.min(h - 30, cy + r2 * Math.sin(angle)));
37505
+ }
37506
+ }
37507
+ forceUpdate((n) => n + 1);
37473
37508
  } else {
37474
37509
  forceUpdate((n) => n + 1);
37475
37510
  }
37476
37511
  return () => {
37477
37512
  cancelAnimationFrame(animRef.current);
37478
37513
  };
37479
- }, [propNodes, propEdges, layout, repulsion, linkDistance, nodeSpacing, showLabels, logicalW, height]);
37514
+ }, [propNodes, propEdges, layout, repulsion, linkDistance, nodeSpacing, showLabels]);
37480
37515
  useEffect(() => {
37481
37516
  const canvas = canvasRef.current;
37482
37517
  if (!canvas) return;
@@ -36631,7 +36631,7 @@ function measureLabelWidth(text) {
36631
36631
  if (!labelMeasureCtx) labelMeasureCtx = document.createElement("canvas").getContext("2d");
36632
36632
  if (!labelMeasureCtx) return text.length * 6;
36633
36633
  labelMeasureCtx.font = "12px system-ui";
36634
- return labelMeasureCtx.measureText(text).width;
36634
+ return labelMeasureCtx.measureText(text).width + 4;
36635
36635
  }
36636
36636
  function getGroupColor(group, groups) {
36637
36637
  if (!group) return GROUP_COLORS2[0];
@@ -36700,6 +36700,7 @@ var init_GraphCanvas = __esm({
36700
36700
  offsetRef.current = offset;
36701
36701
  const [hoveredNode, setHoveredNode] = React73.useState(null);
36702
36702
  const nodesRef = React73.useRef([]);
36703
+ const laidOutRef = React73.useRef(false);
36703
36704
  const [, forceUpdate] = React73.useState(0);
36704
36705
  const [logicalW, setLogicalW] = React73.useState(800);
36705
36706
  React73.useEffect(() => {
@@ -36769,10 +36770,13 @@ var init_GraphCanvas = __esm({
36769
36770
  if (!canvas || propNodes.length === 0) return;
36770
36771
  const w = logicalW;
36771
36772
  const h = height;
36773
+ const prevPos = /* @__PURE__ */ new Map();
36774
+ for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
36772
36775
  const simNodes = propNodes.map((n, idx) => {
36773
- let x = n.x ?? 0;
36774
- let y = n.y ?? 0;
36775
- if (!n.x || !n.y) {
36776
+ const prev = prevPos.get(n.id);
36777
+ let x = prev?.x ?? n.x ?? 0;
36778
+ let y = prev?.y ?? n.y ?? 0;
36779
+ if (!prev && (!n.x || !n.y)) {
36776
36780
  if (layout === "circular") {
36777
36781
  const angle = idx / propNodes.length * 2 * Math.PI;
36778
36782
  const radius = Math.min(w, h) * 0.35;
@@ -36792,8 +36796,12 @@ var init_GraphCanvas = __esm({
36792
36796
  return { ...n, x, y, vx: 0, vy: 0, fx: 0, fy: 0 };
36793
36797
  });
36794
36798
  nodesRef.current = simNodes;
36799
+ const prevIds = /* @__PURE__ */ new Set([...prevPos.keys()]);
36800
+ const newIdList = simNodes.map((n) => n.id);
36801
+ const kept = newIdList.filter((id) => prevIds.has(id)).length;
36802
+ const overlap = prevIds.size === 0 ? 0 : kept / Math.max(prevIds.size, newIdList.length);
36803
+ const fullRelayout = !laidOutRef.current || overlap < 0.5;
36795
36804
  if (layout === "force") {
36796
- let iterations = 0;
36797
36805
  const maxIterations = 300;
36798
36806
  const tick = () => {
36799
36807
  const nodes = nodesRef.current;
@@ -36898,20 +36906,47 @@ var init_GraphCanvas = __esm({
36898
36906
  }
36899
36907
  }
36900
36908
  }
36901
- iterations++;
36902
- forceUpdate((n) => n + 1);
36903
- if (iterations < maxIterations) {
36904
- animRef.current = requestAnimationFrame(tick);
36905
- }
36906
36909
  };
36907
- animRef.current = requestAnimationFrame(tick);
36910
+ if (fullRelayout) {
36911
+ for (let i = 0; i < maxIterations; i++) tick();
36912
+ laidOutRef.current = true;
36913
+ } else {
36914
+ const centroids = /* @__PURE__ */ new Map();
36915
+ for (const node of simNodes) {
36916
+ if (!prevPos.has(node.id)) continue;
36917
+ const g = node.group ?? "__none__";
36918
+ let c = centroids.get(g);
36919
+ if (!c) {
36920
+ c = { x: 0, y: 0, n: 0 };
36921
+ centroids.set(g, c);
36922
+ }
36923
+ c.x += node.x;
36924
+ c.y += node.y;
36925
+ c.n += 1;
36926
+ }
36927
+ const ringCount = /* @__PURE__ */ new Map();
36928
+ for (const node of simNodes) {
36929
+ if (prevPos.has(node.id)) continue;
36930
+ const g = node.group ?? "__none__";
36931
+ const c = centroids.get(g);
36932
+ const cx = c && c.n > 0 ? c.x / c.n : w / 2;
36933
+ const cy = c && c.n > 0 ? c.y / c.n : h / 2;
36934
+ const k = ringCount.get(g) ?? 0;
36935
+ ringCount.set(g, k + 1);
36936
+ const angle = k * 2.399963;
36937
+ const r = 22 + k * 8;
36938
+ node.x = Math.max(30, Math.min(w - 30, cx + r * Math.cos(angle)));
36939
+ node.y = Math.max(30, Math.min(h - 30, cy + r * Math.sin(angle)));
36940
+ }
36941
+ }
36942
+ forceUpdate((n) => n + 1);
36908
36943
  } else {
36909
36944
  forceUpdate((n) => n + 1);
36910
36945
  }
36911
36946
  return () => {
36912
36947
  cancelAnimationFrame(animRef.current);
36913
36948
  };
36914
- }, [propNodes, propEdges, layout, repulsion, linkDistance, nodeSpacing, showLabels, logicalW, height]);
36949
+ }, [propNodes, propEdges, layout, repulsion, linkDistance, nodeSpacing, showLabels]);
36915
36950
  React73.useEffect(() => {
36916
36951
  const canvas = canvasRef.current;
36917
36952
  if (!canvas) return;
@@ -36586,7 +36586,7 @@ function measureLabelWidth(text) {
36586
36586
  if (!labelMeasureCtx) labelMeasureCtx = document.createElement("canvas").getContext("2d");
36587
36587
  if (!labelMeasureCtx) return text.length * 6;
36588
36588
  labelMeasureCtx.font = "12px system-ui";
36589
- return labelMeasureCtx.measureText(text).width;
36589
+ return labelMeasureCtx.measureText(text).width + 4;
36590
36590
  }
36591
36591
  function getGroupColor(group, groups) {
36592
36592
  if (!group) return GROUP_COLORS2[0];
@@ -36655,6 +36655,7 @@ var init_GraphCanvas = __esm({
36655
36655
  offsetRef.current = offset;
36656
36656
  const [hoveredNode, setHoveredNode] = useState(null);
36657
36657
  const nodesRef = useRef([]);
36658
+ const laidOutRef = useRef(false);
36658
36659
  const [, forceUpdate] = useState(0);
36659
36660
  const [logicalW, setLogicalW] = useState(800);
36660
36661
  useEffect(() => {
@@ -36724,10 +36725,13 @@ var init_GraphCanvas = __esm({
36724
36725
  if (!canvas || propNodes.length === 0) return;
36725
36726
  const w = logicalW;
36726
36727
  const h = height;
36728
+ const prevPos = /* @__PURE__ */ new Map();
36729
+ for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
36727
36730
  const simNodes = propNodes.map((n, idx) => {
36728
- let x = n.x ?? 0;
36729
- let y = n.y ?? 0;
36730
- if (!n.x || !n.y) {
36731
+ const prev = prevPos.get(n.id);
36732
+ let x = prev?.x ?? n.x ?? 0;
36733
+ let y = prev?.y ?? n.y ?? 0;
36734
+ if (!prev && (!n.x || !n.y)) {
36731
36735
  if (layout === "circular") {
36732
36736
  const angle = idx / propNodes.length * 2 * Math.PI;
36733
36737
  const radius = Math.min(w, h) * 0.35;
@@ -36747,8 +36751,12 @@ var init_GraphCanvas = __esm({
36747
36751
  return { ...n, x, y, vx: 0, vy: 0, fx: 0, fy: 0 };
36748
36752
  });
36749
36753
  nodesRef.current = simNodes;
36754
+ const prevIds = /* @__PURE__ */ new Set([...prevPos.keys()]);
36755
+ const newIdList = simNodes.map((n) => n.id);
36756
+ const kept = newIdList.filter((id) => prevIds.has(id)).length;
36757
+ const overlap = prevIds.size === 0 ? 0 : kept / Math.max(prevIds.size, newIdList.length);
36758
+ const fullRelayout = !laidOutRef.current || overlap < 0.5;
36750
36759
  if (layout === "force") {
36751
- let iterations = 0;
36752
36760
  const maxIterations = 300;
36753
36761
  const tick = () => {
36754
36762
  const nodes = nodesRef.current;
@@ -36853,20 +36861,47 @@ var init_GraphCanvas = __esm({
36853
36861
  }
36854
36862
  }
36855
36863
  }
36856
- iterations++;
36857
- forceUpdate((n) => n + 1);
36858
- if (iterations < maxIterations) {
36859
- animRef.current = requestAnimationFrame(tick);
36860
- }
36861
36864
  };
36862
- animRef.current = requestAnimationFrame(tick);
36865
+ if (fullRelayout) {
36866
+ for (let i = 0; i < maxIterations; i++) tick();
36867
+ laidOutRef.current = true;
36868
+ } else {
36869
+ const centroids = /* @__PURE__ */ new Map();
36870
+ for (const node of simNodes) {
36871
+ if (!prevPos.has(node.id)) continue;
36872
+ const g = node.group ?? "__none__";
36873
+ let c = centroids.get(g);
36874
+ if (!c) {
36875
+ c = { x: 0, y: 0, n: 0 };
36876
+ centroids.set(g, c);
36877
+ }
36878
+ c.x += node.x;
36879
+ c.y += node.y;
36880
+ c.n += 1;
36881
+ }
36882
+ const ringCount = /* @__PURE__ */ new Map();
36883
+ for (const node of simNodes) {
36884
+ if (prevPos.has(node.id)) continue;
36885
+ const g = node.group ?? "__none__";
36886
+ const c = centroids.get(g);
36887
+ const cx = c && c.n > 0 ? c.x / c.n : w / 2;
36888
+ const cy = c && c.n > 0 ? c.y / c.n : h / 2;
36889
+ const k = ringCount.get(g) ?? 0;
36890
+ ringCount.set(g, k + 1);
36891
+ const angle = k * 2.399963;
36892
+ const r = 22 + k * 8;
36893
+ node.x = Math.max(30, Math.min(w - 30, cx + r * Math.cos(angle)));
36894
+ node.y = Math.max(30, Math.min(h - 30, cy + r * Math.sin(angle)));
36895
+ }
36896
+ }
36897
+ forceUpdate((n) => n + 1);
36863
36898
  } else {
36864
36899
  forceUpdate((n) => n + 1);
36865
36900
  }
36866
36901
  return () => {
36867
36902
  cancelAnimationFrame(animRef.current);
36868
36903
  };
36869
- }, [propNodes, propEdges, layout, repulsion, linkDistance, nodeSpacing, showLabels, logicalW, height]);
36904
+ }, [propNodes, propEdges, layout, repulsion, linkDistance, nodeSpacing, showLabels]);
36870
36905
  useEffect(() => {
36871
36906
  const canvas = canvasRef.current;
36872
36907
  if (!canvas) return;
@@ -35372,7 +35372,7 @@ function measureLabelWidth(text) {
35372
35372
  if (!labelMeasureCtx) labelMeasureCtx = document.createElement("canvas").getContext("2d");
35373
35373
  if (!labelMeasureCtx) return text.length * 6;
35374
35374
  labelMeasureCtx.font = "12px system-ui";
35375
- return labelMeasureCtx.measureText(text).width;
35375
+ return labelMeasureCtx.measureText(text).width + 4;
35376
35376
  }
35377
35377
  function getGroupColor(group, groups) {
35378
35378
  if (!group) return GROUP_COLORS2[0];
@@ -35441,6 +35441,7 @@ var init_GraphCanvas = __esm({
35441
35441
  offsetRef.current = offset;
35442
35442
  const [hoveredNode, setHoveredNode] = React83.useState(null);
35443
35443
  const nodesRef = React83.useRef([]);
35444
+ const laidOutRef = React83.useRef(false);
35444
35445
  const [, forceUpdate] = React83.useState(0);
35445
35446
  const [logicalW, setLogicalW] = React83.useState(800);
35446
35447
  React83.useEffect(() => {
@@ -35510,10 +35511,13 @@ var init_GraphCanvas = __esm({
35510
35511
  if (!canvas || propNodes.length === 0) return;
35511
35512
  const w = logicalW;
35512
35513
  const h = height;
35514
+ const prevPos = /* @__PURE__ */ new Map();
35515
+ for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
35513
35516
  const simNodes = propNodes.map((n, idx) => {
35514
- let x = n.x ?? 0;
35515
- let y = n.y ?? 0;
35516
- if (!n.x || !n.y) {
35517
+ const prev = prevPos.get(n.id);
35518
+ let x = prev?.x ?? n.x ?? 0;
35519
+ let y = prev?.y ?? n.y ?? 0;
35520
+ if (!prev && (!n.x || !n.y)) {
35517
35521
  if (layout === "circular") {
35518
35522
  const angle = idx / propNodes.length * 2 * Math.PI;
35519
35523
  const radius = Math.min(w, h) * 0.35;
@@ -35533,8 +35537,12 @@ var init_GraphCanvas = __esm({
35533
35537
  return { ...n, x, y, vx: 0, vy: 0, fx: 0, fy: 0 };
35534
35538
  });
35535
35539
  nodesRef.current = simNodes;
35540
+ const prevIds = /* @__PURE__ */ new Set([...prevPos.keys()]);
35541
+ const newIdList = simNodes.map((n) => n.id);
35542
+ const kept = newIdList.filter((id) => prevIds.has(id)).length;
35543
+ const overlap = prevIds.size === 0 ? 0 : kept / Math.max(prevIds.size, newIdList.length);
35544
+ const fullRelayout = !laidOutRef.current || overlap < 0.5;
35536
35545
  if (layout === "force") {
35537
- let iterations = 0;
35538
35546
  const maxIterations = 300;
35539
35547
  const tick = () => {
35540
35548
  const nodes = nodesRef.current;
@@ -35639,20 +35647,47 @@ var init_GraphCanvas = __esm({
35639
35647
  }
35640
35648
  }
35641
35649
  }
35642
- iterations++;
35643
- forceUpdate((n) => n + 1);
35644
- if (iterations < maxIterations) {
35645
- animRef.current = requestAnimationFrame(tick);
35646
- }
35647
35650
  };
35648
- animRef.current = requestAnimationFrame(tick);
35651
+ if (fullRelayout) {
35652
+ for (let i = 0; i < maxIterations; i++) tick();
35653
+ laidOutRef.current = true;
35654
+ } else {
35655
+ const centroids = /* @__PURE__ */ new Map();
35656
+ for (const node of simNodes) {
35657
+ if (!prevPos.has(node.id)) continue;
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 ringCount = /* @__PURE__ */ new Map();
35669
+ for (const node of simNodes) {
35670
+ if (prevPos.has(node.id)) continue;
35671
+ const g = node.group ?? "__none__";
35672
+ const c = centroids.get(g);
35673
+ const cx = c && c.n > 0 ? c.x / c.n : w / 2;
35674
+ const cy = c && c.n > 0 ? c.y / c.n : h / 2;
35675
+ const k = ringCount.get(g) ?? 0;
35676
+ ringCount.set(g, k + 1);
35677
+ const angle = k * 2.399963;
35678
+ const r = 22 + k * 8;
35679
+ node.x = Math.max(30, Math.min(w - 30, cx + r * Math.cos(angle)));
35680
+ node.y = Math.max(30, Math.min(h - 30, cy + r * Math.sin(angle)));
35681
+ }
35682
+ }
35683
+ forceUpdate((n) => n + 1);
35649
35684
  } else {
35650
35685
  forceUpdate((n) => n + 1);
35651
35686
  }
35652
35687
  return () => {
35653
35688
  cancelAnimationFrame(animRef.current);
35654
35689
  };
35655
- }, [propNodes, propEdges, layout, repulsion, linkDistance, nodeSpacing, showLabels, logicalW, height]);
35690
+ }, [propNodes, propEdges, layout, repulsion, linkDistance, nodeSpacing, showLabels]);
35656
35691
  React83.useEffect(() => {
35657
35692
  const canvas = canvasRef.current;
35658
35693
  if (!canvas) return;
@@ -35327,7 +35327,7 @@ function measureLabelWidth(text) {
35327
35327
  if (!labelMeasureCtx) labelMeasureCtx = document.createElement("canvas").getContext("2d");
35328
35328
  if (!labelMeasureCtx) return text.length * 6;
35329
35329
  labelMeasureCtx.font = "12px system-ui";
35330
- return labelMeasureCtx.measureText(text).width;
35330
+ return labelMeasureCtx.measureText(text).width + 4;
35331
35331
  }
35332
35332
  function getGroupColor(group, groups) {
35333
35333
  if (!group) return GROUP_COLORS2[0];
@@ -35396,6 +35396,7 @@ var init_GraphCanvas = __esm({
35396
35396
  offsetRef.current = offset;
35397
35397
  const [hoveredNode, setHoveredNode] = useState(null);
35398
35398
  const nodesRef = useRef([]);
35399
+ const laidOutRef = useRef(false);
35399
35400
  const [, forceUpdate] = useState(0);
35400
35401
  const [logicalW, setLogicalW] = useState(800);
35401
35402
  useEffect(() => {
@@ -35465,10 +35466,13 @@ var init_GraphCanvas = __esm({
35465
35466
  if (!canvas || propNodes.length === 0) return;
35466
35467
  const w = logicalW;
35467
35468
  const h = height;
35469
+ const prevPos = /* @__PURE__ */ new Map();
35470
+ for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
35468
35471
  const simNodes = propNodes.map((n, idx) => {
35469
- let x = n.x ?? 0;
35470
- let y = n.y ?? 0;
35471
- if (!n.x || !n.y) {
35472
+ const prev = prevPos.get(n.id);
35473
+ let x = prev?.x ?? n.x ?? 0;
35474
+ let y = prev?.y ?? n.y ?? 0;
35475
+ if (!prev && (!n.x || !n.y)) {
35472
35476
  if (layout === "circular") {
35473
35477
  const angle = idx / propNodes.length * 2 * Math.PI;
35474
35478
  const radius = Math.min(w, h) * 0.35;
@@ -35488,8 +35492,12 @@ var init_GraphCanvas = __esm({
35488
35492
  return { ...n, x, y, vx: 0, vy: 0, fx: 0, fy: 0 };
35489
35493
  });
35490
35494
  nodesRef.current = simNodes;
35495
+ const prevIds = /* @__PURE__ */ new Set([...prevPos.keys()]);
35496
+ const newIdList = simNodes.map((n) => n.id);
35497
+ const kept = newIdList.filter((id) => prevIds.has(id)).length;
35498
+ const overlap = prevIds.size === 0 ? 0 : kept / Math.max(prevIds.size, newIdList.length);
35499
+ const fullRelayout = !laidOutRef.current || overlap < 0.5;
35491
35500
  if (layout === "force") {
35492
- let iterations = 0;
35493
35501
  const maxIterations = 300;
35494
35502
  const tick = () => {
35495
35503
  const nodes = nodesRef.current;
@@ -35594,20 +35602,47 @@ var init_GraphCanvas = __esm({
35594
35602
  }
35595
35603
  }
35596
35604
  }
35597
- iterations++;
35598
- forceUpdate((n) => n + 1);
35599
- if (iterations < maxIterations) {
35600
- animRef.current = requestAnimationFrame(tick);
35601
- }
35602
35605
  };
35603
- animRef.current = requestAnimationFrame(tick);
35606
+ if (fullRelayout) {
35607
+ for (let i = 0; i < maxIterations; i++) tick();
35608
+ laidOutRef.current = true;
35609
+ } else {
35610
+ const centroids = /* @__PURE__ */ new Map();
35611
+ for (const node of simNodes) {
35612
+ if (!prevPos.has(node.id)) continue;
35613
+ const g = node.group ?? "__none__";
35614
+ let c = centroids.get(g);
35615
+ if (!c) {
35616
+ c = { x: 0, y: 0, n: 0 };
35617
+ centroids.set(g, c);
35618
+ }
35619
+ c.x += node.x;
35620
+ c.y += node.y;
35621
+ c.n += 1;
35622
+ }
35623
+ const ringCount = /* @__PURE__ */ new Map();
35624
+ for (const node of simNodes) {
35625
+ if (prevPos.has(node.id)) continue;
35626
+ const g = node.group ?? "__none__";
35627
+ const c = centroids.get(g);
35628
+ const cx = c && c.n > 0 ? c.x / c.n : w / 2;
35629
+ const cy = c && c.n > 0 ? c.y / c.n : h / 2;
35630
+ const k = ringCount.get(g) ?? 0;
35631
+ ringCount.set(g, k + 1);
35632
+ const angle = k * 2.399963;
35633
+ const r = 22 + k * 8;
35634
+ node.x = Math.max(30, Math.min(w - 30, cx + r * Math.cos(angle)));
35635
+ node.y = Math.max(30, Math.min(h - 30, cy + r * Math.sin(angle)));
35636
+ }
35637
+ }
35638
+ forceUpdate((n) => n + 1);
35604
35639
  } else {
35605
35640
  forceUpdate((n) => n + 1);
35606
35641
  }
35607
35642
  return () => {
35608
35643
  cancelAnimationFrame(animRef.current);
35609
35644
  };
35610
- }, [propNodes, propEdges, layout, repulsion, linkDistance, nodeSpacing, showLabels, logicalW, height]);
35645
+ }, [propNodes, propEdges, layout, repulsion, linkDistance, nodeSpacing, showLabels]);
35611
35646
  useEffect(() => {
35612
35647
  const canvas = canvasRef.current;
35613
35648
  if (!canvas) return;
@@ -34737,7 +34737,7 @@ function measureLabelWidth(text) {
34737
34737
  if (!labelMeasureCtx) labelMeasureCtx = document.createElement("canvas").getContext("2d");
34738
34738
  if (!labelMeasureCtx) return text.length * 6;
34739
34739
  labelMeasureCtx.font = "12px system-ui";
34740
- return labelMeasureCtx.measureText(text).width;
34740
+ return labelMeasureCtx.measureText(text).width + 4;
34741
34741
  }
34742
34742
  function getGroupColor(group, groups) {
34743
34743
  if (!group) return GROUP_COLORS2[0];
@@ -34806,6 +34806,7 @@ var init_GraphCanvas = __esm({
34806
34806
  offsetRef.current = offset;
34807
34807
  const [hoveredNode, setHoveredNode] = React81.useState(null);
34808
34808
  const nodesRef = React81.useRef([]);
34809
+ const laidOutRef = React81.useRef(false);
34809
34810
  const [, forceUpdate] = React81.useState(0);
34810
34811
  const [logicalW, setLogicalW] = React81.useState(800);
34811
34812
  React81.useEffect(() => {
@@ -34875,10 +34876,13 @@ var init_GraphCanvas = __esm({
34875
34876
  if (!canvas || propNodes.length === 0) return;
34876
34877
  const w = logicalW;
34877
34878
  const h = height;
34879
+ const prevPos = /* @__PURE__ */ new Map();
34880
+ for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
34878
34881
  const simNodes = propNodes.map((n, idx) => {
34879
- let x = n.x ?? 0;
34880
- let y = n.y ?? 0;
34881
- if (!n.x || !n.y) {
34882
+ const prev = prevPos.get(n.id);
34883
+ let x = prev?.x ?? n.x ?? 0;
34884
+ let y = prev?.y ?? n.y ?? 0;
34885
+ if (!prev && (!n.x || !n.y)) {
34882
34886
  if (layout === "circular") {
34883
34887
  const angle = idx / propNodes.length * 2 * Math.PI;
34884
34888
  const radius = Math.min(w, h) * 0.35;
@@ -34898,8 +34902,12 @@ var init_GraphCanvas = __esm({
34898
34902
  return { ...n, x, y, vx: 0, vy: 0, fx: 0, fy: 0 };
34899
34903
  });
34900
34904
  nodesRef.current = simNodes;
34905
+ const prevIds = /* @__PURE__ */ new Set([...prevPos.keys()]);
34906
+ const newIdList = simNodes.map((n) => n.id);
34907
+ const kept = newIdList.filter((id) => prevIds.has(id)).length;
34908
+ const overlap = prevIds.size === 0 ? 0 : kept / Math.max(prevIds.size, newIdList.length);
34909
+ const fullRelayout = !laidOutRef.current || overlap < 0.5;
34901
34910
  if (layout === "force") {
34902
- let iterations = 0;
34903
34911
  const maxIterations = 300;
34904
34912
  const tick = () => {
34905
34913
  const nodes = nodesRef.current;
@@ -35004,20 +35012,47 @@ var init_GraphCanvas = __esm({
35004
35012
  }
35005
35013
  }
35006
35014
  }
35007
- iterations++;
35008
- forceUpdate((n) => n + 1);
35009
- if (iterations < maxIterations) {
35010
- animRef.current = requestAnimationFrame(tick);
35011
- }
35012
35015
  };
35013
- animRef.current = requestAnimationFrame(tick);
35016
+ if (fullRelayout) {
35017
+ for (let i = 0; i < maxIterations; i++) tick();
35018
+ laidOutRef.current = true;
35019
+ } else {
35020
+ const centroids = /* @__PURE__ */ new Map();
35021
+ for (const node of simNodes) {
35022
+ if (!prevPos.has(node.id)) continue;
35023
+ const g = node.group ?? "__none__";
35024
+ let c = centroids.get(g);
35025
+ if (!c) {
35026
+ c = { x: 0, y: 0, n: 0 };
35027
+ centroids.set(g, c);
35028
+ }
35029
+ c.x += node.x;
35030
+ c.y += node.y;
35031
+ c.n += 1;
35032
+ }
35033
+ const ringCount = /* @__PURE__ */ new Map();
35034
+ for (const node of simNodes) {
35035
+ if (prevPos.has(node.id)) continue;
35036
+ const g = node.group ?? "__none__";
35037
+ const c = centroids.get(g);
35038
+ const cx = c && c.n > 0 ? c.x / c.n : w / 2;
35039
+ const cy = c && c.n > 0 ? c.y / c.n : h / 2;
35040
+ const k = ringCount.get(g) ?? 0;
35041
+ ringCount.set(g, k + 1);
35042
+ const angle = k * 2.399963;
35043
+ const r = 22 + k * 8;
35044
+ node.x = Math.max(30, Math.min(w - 30, cx + r * Math.cos(angle)));
35045
+ node.y = Math.max(30, Math.min(h - 30, cy + r * Math.sin(angle)));
35046
+ }
35047
+ }
35048
+ forceUpdate((n) => n + 1);
35014
35049
  } else {
35015
35050
  forceUpdate((n) => n + 1);
35016
35051
  }
35017
35052
  return () => {
35018
35053
  cancelAnimationFrame(animRef.current);
35019
35054
  };
35020
- }, [propNodes, propEdges, layout, repulsion, linkDistance, nodeSpacing, showLabels, logicalW, height]);
35055
+ }, [propNodes, propEdges, layout, repulsion, linkDistance, nodeSpacing, showLabels]);
35021
35056
  React81.useEffect(() => {
35022
35057
  const canvas = canvasRef.current;
35023
35058
  if (!canvas) return;
@@ -34693,7 +34693,7 @@ function measureLabelWidth(text) {
34693
34693
  if (!labelMeasureCtx) labelMeasureCtx = document.createElement("canvas").getContext("2d");
34694
34694
  if (!labelMeasureCtx) return text.length * 6;
34695
34695
  labelMeasureCtx.font = "12px system-ui";
34696
- return labelMeasureCtx.measureText(text).width;
34696
+ return labelMeasureCtx.measureText(text).width + 4;
34697
34697
  }
34698
34698
  function getGroupColor(group, groups) {
34699
34699
  if (!group) return GROUP_COLORS2[0];
@@ -34762,6 +34762,7 @@ var init_GraphCanvas = __esm({
34762
34762
  offsetRef.current = offset;
34763
34763
  const [hoveredNode, setHoveredNode] = useState(null);
34764
34764
  const nodesRef = useRef([]);
34765
+ const laidOutRef = useRef(false);
34765
34766
  const [, forceUpdate] = useState(0);
34766
34767
  const [logicalW, setLogicalW] = useState(800);
34767
34768
  useEffect(() => {
@@ -34831,10 +34832,13 @@ var init_GraphCanvas = __esm({
34831
34832
  if (!canvas || propNodes.length === 0) return;
34832
34833
  const w = logicalW;
34833
34834
  const h = height;
34835
+ const prevPos = /* @__PURE__ */ new Map();
34836
+ for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
34834
34837
  const simNodes = propNodes.map((n, idx) => {
34835
- let x = n.x ?? 0;
34836
- let y = n.y ?? 0;
34837
- if (!n.x || !n.y) {
34838
+ const prev = prevPos.get(n.id);
34839
+ let x = prev?.x ?? n.x ?? 0;
34840
+ let y = prev?.y ?? n.y ?? 0;
34841
+ if (!prev && (!n.x || !n.y)) {
34838
34842
  if (layout === "circular") {
34839
34843
  const angle = idx / propNodes.length * 2 * Math.PI;
34840
34844
  const radius = Math.min(w, h) * 0.35;
@@ -34854,8 +34858,12 @@ var init_GraphCanvas = __esm({
34854
34858
  return { ...n, x, y, vx: 0, vy: 0, fx: 0, fy: 0 };
34855
34859
  });
34856
34860
  nodesRef.current = simNodes;
34861
+ const prevIds = /* @__PURE__ */ new Set([...prevPos.keys()]);
34862
+ const newIdList = simNodes.map((n) => n.id);
34863
+ const kept = newIdList.filter((id) => prevIds.has(id)).length;
34864
+ const overlap = prevIds.size === 0 ? 0 : kept / Math.max(prevIds.size, newIdList.length);
34865
+ const fullRelayout = !laidOutRef.current || overlap < 0.5;
34857
34866
  if (layout === "force") {
34858
- let iterations = 0;
34859
34867
  const maxIterations = 300;
34860
34868
  const tick = () => {
34861
34869
  const nodes = nodesRef.current;
@@ -34960,20 +34968,47 @@ var init_GraphCanvas = __esm({
34960
34968
  }
34961
34969
  }
34962
34970
  }
34963
- iterations++;
34964
- forceUpdate((n) => n + 1);
34965
- if (iterations < maxIterations) {
34966
- animRef.current = requestAnimationFrame(tick);
34967
- }
34968
34971
  };
34969
- animRef.current = requestAnimationFrame(tick);
34972
+ if (fullRelayout) {
34973
+ for (let i = 0; i < maxIterations; i++) tick();
34974
+ laidOutRef.current = true;
34975
+ } else {
34976
+ const centroids = /* @__PURE__ */ new Map();
34977
+ for (const node of simNodes) {
34978
+ if (!prevPos.has(node.id)) continue;
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 ringCount = /* @__PURE__ */ new Map();
34990
+ for (const node of simNodes) {
34991
+ if (prevPos.has(node.id)) continue;
34992
+ const g = node.group ?? "__none__";
34993
+ const c = centroids.get(g);
34994
+ const cx = c && c.n > 0 ? c.x / c.n : w / 2;
34995
+ const cy = c && c.n > 0 ? c.y / c.n : h / 2;
34996
+ const k = ringCount.get(g) ?? 0;
34997
+ ringCount.set(g, k + 1);
34998
+ const angle = k * 2.399963;
34999
+ const r = 22 + k * 8;
35000
+ node.x = Math.max(30, Math.min(w - 30, cx + r * Math.cos(angle)));
35001
+ node.y = Math.max(30, Math.min(h - 30, cy + r * Math.sin(angle)));
35002
+ }
35003
+ }
35004
+ forceUpdate((n) => n + 1);
34970
35005
  } else {
34971
35006
  forceUpdate((n) => n + 1);
34972
35007
  }
34973
35008
  return () => {
34974
35009
  cancelAnimationFrame(animRef.current);
34975
35010
  };
34976
- }, [propNodes, propEdges, layout, repulsion, linkDistance, nodeSpacing, showLabels, logicalW, height]);
35011
+ }, [propNodes, propEdges, layout, repulsion, linkDistance, nodeSpacing, showLabels]);
34977
35012
  useEffect(() => {
34978
35013
  const canvas = canvasRef.current;
34979
35014
  if (!canvas) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.116.0",
3
+ "version": "5.117.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [