microboard-temp 0.13.15 → 0.13.16

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.
@@ -4675,6 +4675,11 @@ var conf = {
4675
4675
  DEFAULT_GAME_ITEM_DIMENSIONS: { width: 200, height: 200 },
4676
4676
  MAX_CARD_SIZE: 500,
4677
4677
  CONNECTOR_ITEM_OFFSET: 20,
4678
+ FG_SPRING_K: 3,
4679
+ FG_TARGET_GAP: 60,
4680
+ FG_REPULSION: 400000,
4681
+ FG_DAMPING: 0.8,
4682
+ FG_SLEEP_THRESHOLD: 0.5,
4678
4683
  GRAVITY_G: 80,
4679
4684
  GRAVITY_G_CENTER: 120,
4680
4685
  GRAVITY_DAMPING: 0.96,
@@ -54569,6 +54574,176 @@ class GravityEngine {
54569
54574
  }
54570
54575
  }
54571
54576
 
54577
+ // src/ForceGraph/ForceGraphEngine.ts
54578
+ var EXCLUDED_TYPES = new Set(["Connector", "Comment"]);
54579
+
54580
+ class ForceGraphEngine {
54581
+ board;
54582
+ velocities = new Map;
54583
+ tickTimer = null;
54584
+ syncTimer = null;
54585
+ lastSyncedPositions = new Map;
54586
+ TICK_MS = 33;
54587
+ SYNC_MS = 300;
54588
+ SOFTENING_SQ = 100 * 100;
54589
+ MIN_MOVE_PX = 0.05;
54590
+ constructor(board) {
54591
+ this.board = board;
54592
+ }
54593
+ start() {
54594
+ if (this.tickTimer !== null)
54595
+ return;
54596
+ for (const item of this.getNodes()) {
54597
+ const pos = item.transformation.getTranslation();
54598
+ this.lastSyncedPositions.set(item.getId(), { x: pos.x, y: pos.y });
54599
+ this.velocities.set(item.getId(), { vx: 0, vy: 0 });
54600
+ }
54601
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54602
+ this.syncTimer = setInterval(() => this.syncPositions(), this.SYNC_MS);
54603
+ }
54604
+ stop() {
54605
+ if (this.tickTimer !== null) {
54606
+ clearInterval(this.tickTimer);
54607
+ this.tickTimer = null;
54608
+ }
54609
+ if (this.syncTimer !== null) {
54610
+ clearInterval(this.syncTimer);
54611
+ this.syncTimer = null;
54612
+ }
54613
+ this.syncPositions();
54614
+ this.velocities.clear();
54615
+ this.lastSyncedPositions.clear();
54616
+ }
54617
+ getNodes() {
54618
+ return this.board.items.listAll().filter((item) => !EXCLUDED_TYPES.has(item.itemType) && !item.transformation.isLocked);
54619
+ }
54620
+ getConnectors() {
54621
+ return this.board.items.listAll().filter((item) => item.itemType === "Connector");
54622
+ }
54623
+ tick() {
54624
+ const dt = this.TICK_MS / 1000;
54625
+ const nodes = this.getNodes();
54626
+ if (nodes.length < 2)
54627
+ return;
54628
+ const snapMap = new Map;
54629
+ for (const item of nodes) {
54630
+ const pos = item.transformation.getTranslation();
54631
+ const mbr = item.getMbr();
54632
+ const w = Math.max(mbr.getWidth(), 1);
54633
+ const h2 = Math.max(mbr.getHeight(), 1);
54634
+ snapMap.set(item.getId(), {
54635
+ id: item.getId(),
54636
+ cx: pos.x + w * 0.5,
54637
+ cy: pos.y + h2 * 0.5,
54638
+ w,
54639
+ h: h2
54640
+ });
54641
+ }
54642
+ const snap = Array.from(snapMap.values());
54643
+ const ax = new Map;
54644
+ const ay = new Map;
54645
+ for (const s2 of snap) {
54646
+ ax.set(s2.id, 0);
54647
+ ay.set(s2.id, 0);
54648
+ }
54649
+ for (const connector of this.getConnectors()) {
54650
+ const { startItem, endItem } = connector.getConnectedItems();
54651
+ if (!startItem || !endItem)
54652
+ continue;
54653
+ const s1 = snapMap.get(startItem.getId());
54654
+ const s2 = snapMap.get(endItem.getId());
54655
+ if (!s1 || !s2)
54656
+ continue;
54657
+ const dx = s2.cx - s1.cx;
54658
+ const dy = s2.cy - s1.cy;
54659
+ const dist = Math.sqrt(dx * dx + dy * dy) + 0.001;
54660
+ const targetDist = (Math.max(s1.w, s1.h) + Math.max(s2.w, s2.h)) * 0.5 + conf.FG_TARGET_GAP;
54661
+ const stretch = dist - targetDist;
54662
+ const forceMag = stretch * conf.FG_SPRING_K;
54663
+ const fx = dx / dist * forceMag;
54664
+ const fy = dy / dist * forceMag;
54665
+ ax.set(s1.id, (ax.get(s1.id) ?? 0) + fx);
54666
+ ay.set(s1.id, (ay.get(s1.id) ?? 0) + fy);
54667
+ ax.set(s2.id, (ax.get(s2.id) ?? 0) - fx);
54668
+ ay.set(s2.id, (ay.get(s2.id) ?? 0) - fy);
54669
+ }
54670
+ for (let i = 0;i < snap.length; i++) {
54671
+ for (let j = i + 1;j < snap.length; j++) {
54672
+ const s1 = snap[i];
54673
+ const s2 = snap[j];
54674
+ const dx = s2.cx - s1.cx;
54675
+ const dy = s2.cy - s1.cy;
54676
+ const distSq = dx * dx + dy * dy + this.SOFTENING_SQ;
54677
+ const repMag = conf.FG_REPULSION / distSq;
54678
+ const fx = dx * repMag;
54679
+ const fy = dy * repMag;
54680
+ ax.set(s1.id, (ax.get(s1.id) ?? 0) - fx);
54681
+ ay.set(s1.id, (ay.get(s1.id) ?? 0) - fy);
54682
+ ax.set(s2.id, (ax.get(s2.id) ?? 0) + fx);
54683
+ ay.set(s2.id, (ay.get(s2.id) ?? 0) + fy);
54684
+ }
54685
+ }
54686
+ let totalEnergy = 0;
54687
+ for (const item of nodes) {
54688
+ const id = item.getId();
54689
+ if (!this.velocities.has(id)) {
54690
+ this.velocities.set(id, { vx: 0, vy: 0 });
54691
+ }
54692
+ const vel = this.velocities.get(id);
54693
+ vel.vx = (vel.vx + (ax.get(id) ?? 0) * dt) * conf.FG_DAMPING;
54694
+ vel.vy = (vel.vy + (ay.get(id) ?? 0) * dt) * conf.FG_DAMPING;
54695
+ totalEnergy += vel.vx * vel.vx + vel.vy * vel.vy;
54696
+ const moveX = vel.vx * dt;
54697
+ const moveY = vel.vy * dt;
54698
+ if (Math.abs(moveX) >= this.MIN_MOVE_PX || Math.abs(moveY) >= this.MIN_MOVE_PX) {
54699
+ item.transformation.applyMatrixSilent({
54700
+ translateX: moveX,
54701
+ translateY: moveY,
54702
+ scaleX: 1,
54703
+ scaleY: 1,
54704
+ shearX: 0,
54705
+ shearY: 0
54706
+ });
54707
+ }
54708
+ }
54709
+ if (totalEnergy < conf.FG_SLEEP_THRESHOLD && this.tickTimer !== null) {
54710
+ clearInterval(this.tickTimer);
54711
+ this.tickTimer = null;
54712
+ this.syncPositions();
54713
+ }
54714
+ }
54715
+ syncPositions() {
54716
+ const nodes = this.getNodes();
54717
+ if (nodes.length === 0)
54718
+ return;
54719
+ const movedItems = nodes.map((item) => {
54720
+ const id = item.getId();
54721
+ const pos = item.transformation.getTranslation();
54722
+ const last = this.lastSyncedPositions.get(id);
54723
+ const dx = last ? pos.x - last.x : 0;
54724
+ const dy = last ? pos.y - last.y : 0;
54725
+ this.lastSyncedPositions.set(id, { x: pos.x, y: pos.y });
54726
+ return { id, dx, dy };
54727
+ }).filter(({ dx, dy }) => Math.abs(dx) > 0.5 || Math.abs(dy) > 0.5);
54728
+ if (movedItems.length === 0)
54729
+ return;
54730
+ const operation = {
54731
+ class: "Transformation",
54732
+ method: "applyMatrix",
54733
+ items: movedItems.map(({ id, dx, dy }) => ({
54734
+ id,
54735
+ matrix: { translateX: dx, translateY: dy, scaleX: 1, scaleY: 1, shearX: 0, shearY: 0 }
54736
+ }))
54737
+ };
54738
+ this.board.events.emit(operation);
54739
+ }
54740
+ wake() {
54741
+ if (this.tickTimer === null && this.syncTimer !== null) {
54742
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54743
+ }
54744
+ }
54745
+ }
54746
+
54572
54747
  // src/Board.ts
54573
54748
  class Board {
54574
54749
  boardId;
@@ -55641,6 +55816,25 @@ class Board {
55641
55816
  isGravityEnabled() {
55642
55817
  return this.gravity !== null;
55643
55818
  }
55819
+ forceGraph = null;
55820
+ enableForceGraph() {
55821
+ if (this.forceGraph)
55822
+ return;
55823
+ this.forceGraph = new ForceGraphEngine(this);
55824
+ this.forceGraph.start();
55825
+ }
55826
+ disableForceGraph() {
55827
+ if (!this.forceGraph)
55828
+ return;
55829
+ this.forceGraph.stop();
55830
+ this.forceGraph = null;
55831
+ }
55832
+ isForceGraphEnabled() {
55833
+ return this.forceGraph !== null;
55834
+ }
55835
+ wakeForceGraph() {
55836
+ this.forceGraph?.wake();
55837
+ }
55644
55838
  }
55645
55839
  // src/Events/Merge.ts
55646
55840
  var import_slate36 = require("slate");
package/dist/cjs/index.js CHANGED
@@ -4675,6 +4675,11 @@ var conf = {
4675
4675
  DEFAULT_GAME_ITEM_DIMENSIONS: { width: 200, height: 200 },
4676
4676
  MAX_CARD_SIZE: 500,
4677
4677
  CONNECTOR_ITEM_OFFSET: 20,
4678
+ FG_SPRING_K: 3,
4679
+ FG_TARGET_GAP: 60,
4680
+ FG_REPULSION: 400000,
4681
+ FG_DAMPING: 0.8,
4682
+ FG_SLEEP_THRESHOLD: 0.5,
4678
4683
  GRAVITY_G: 80,
4679
4684
  GRAVITY_G_CENTER: 120,
4680
4685
  GRAVITY_DAMPING: 0.96,
@@ -54569,6 +54574,176 @@ class GravityEngine {
54569
54574
  }
54570
54575
  }
54571
54576
 
54577
+ // src/ForceGraph/ForceGraphEngine.ts
54578
+ var EXCLUDED_TYPES = new Set(["Connector", "Comment"]);
54579
+
54580
+ class ForceGraphEngine {
54581
+ board;
54582
+ velocities = new Map;
54583
+ tickTimer = null;
54584
+ syncTimer = null;
54585
+ lastSyncedPositions = new Map;
54586
+ TICK_MS = 33;
54587
+ SYNC_MS = 300;
54588
+ SOFTENING_SQ = 100 * 100;
54589
+ MIN_MOVE_PX = 0.05;
54590
+ constructor(board) {
54591
+ this.board = board;
54592
+ }
54593
+ start() {
54594
+ if (this.tickTimer !== null)
54595
+ return;
54596
+ for (const item of this.getNodes()) {
54597
+ const pos = item.transformation.getTranslation();
54598
+ this.lastSyncedPositions.set(item.getId(), { x: pos.x, y: pos.y });
54599
+ this.velocities.set(item.getId(), { vx: 0, vy: 0 });
54600
+ }
54601
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54602
+ this.syncTimer = setInterval(() => this.syncPositions(), this.SYNC_MS);
54603
+ }
54604
+ stop() {
54605
+ if (this.tickTimer !== null) {
54606
+ clearInterval(this.tickTimer);
54607
+ this.tickTimer = null;
54608
+ }
54609
+ if (this.syncTimer !== null) {
54610
+ clearInterval(this.syncTimer);
54611
+ this.syncTimer = null;
54612
+ }
54613
+ this.syncPositions();
54614
+ this.velocities.clear();
54615
+ this.lastSyncedPositions.clear();
54616
+ }
54617
+ getNodes() {
54618
+ return this.board.items.listAll().filter((item) => !EXCLUDED_TYPES.has(item.itemType) && !item.transformation.isLocked);
54619
+ }
54620
+ getConnectors() {
54621
+ return this.board.items.listAll().filter((item) => item.itemType === "Connector");
54622
+ }
54623
+ tick() {
54624
+ const dt = this.TICK_MS / 1000;
54625
+ const nodes = this.getNodes();
54626
+ if (nodes.length < 2)
54627
+ return;
54628
+ const snapMap = new Map;
54629
+ for (const item of nodes) {
54630
+ const pos = item.transformation.getTranslation();
54631
+ const mbr = item.getMbr();
54632
+ const w = Math.max(mbr.getWidth(), 1);
54633
+ const h2 = Math.max(mbr.getHeight(), 1);
54634
+ snapMap.set(item.getId(), {
54635
+ id: item.getId(),
54636
+ cx: pos.x + w * 0.5,
54637
+ cy: pos.y + h2 * 0.5,
54638
+ w,
54639
+ h: h2
54640
+ });
54641
+ }
54642
+ const snap = Array.from(snapMap.values());
54643
+ const ax = new Map;
54644
+ const ay = new Map;
54645
+ for (const s2 of snap) {
54646
+ ax.set(s2.id, 0);
54647
+ ay.set(s2.id, 0);
54648
+ }
54649
+ for (const connector of this.getConnectors()) {
54650
+ const { startItem, endItem } = connector.getConnectedItems();
54651
+ if (!startItem || !endItem)
54652
+ continue;
54653
+ const s1 = snapMap.get(startItem.getId());
54654
+ const s2 = snapMap.get(endItem.getId());
54655
+ if (!s1 || !s2)
54656
+ continue;
54657
+ const dx = s2.cx - s1.cx;
54658
+ const dy = s2.cy - s1.cy;
54659
+ const dist = Math.sqrt(dx * dx + dy * dy) + 0.001;
54660
+ const targetDist = (Math.max(s1.w, s1.h) + Math.max(s2.w, s2.h)) * 0.5 + conf.FG_TARGET_GAP;
54661
+ const stretch = dist - targetDist;
54662
+ const forceMag = stretch * conf.FG_SPRING_K;
54663
+ const fx = dx / dist * forceMag;
54664
+ const fy = dy / dist * forceMag;
54665
+ ax.set(s1.id, (ax.get(s1.id) ?? 0) + fx);
54666
+ ay.set(s1.id, (ay.get(s1.id) ?? 0) + fy);
54667
+ ax.set(s2.id, (ax.get(s2.id) ?? 0) - fx);
54668
+ ay.set(s2.id, (ay.get(s2.id) ?? 0) - fy);
54669
+ }
54670
+ for (let i = 0;i < snap.length; i++) {
54671
+ for (let j = i + 1;j < snap.length; j++) {
54672
+ const s1 = snap[i];
54673
+ const s2 = snap[j];
54674
+ const dx = s2.cx - s1.cx;
54675
+ const dy = s2.cy - s1.cy;
54676
+ const distSq = dx * dx + dy * dy + this.SOFTENING_SQ;
54677
+ const repMag = conf.FG_REPULSION / distSq;
54678
+ const fx = dx * repMag;
54679
+ const fy = dy * repMag;
54680
+ ax.set(s1.id, (ax.get(s1.id) ?? 0) - fx);
54681
+ ay.set(s1.id, (ay.get(s1.id) ?? 0) - fy);
54682
+ ax.set(s2.id, (ax.get(s2.id) ?? 0) + fx);
54683
+ ay.set(s2.id, (ay.get(s2.id) ?? 0) + fy);
54684
+ }
54685
+ }
54686
+ let totalEnergy = 0;
54687
+ for (const item of nodes) {
54688
+ const id = item.getId();
54689
+ if (!this.velocities.has(id)) {
54690
+ this.velocities.set(id, { vx: 0, vy: 0 });
54691
+ }
54692
+ const vel = this.velocities.get(id);
54693
+ vel.vx = (vel.vx + (ax.get(id) ?? 0) * dt) * conf.FG_DAMPING;
54694
+ vel.vy = (vel.vy + (ay.get(id) ?? 0) * dt) * conf.FG_DAMPING;
54695
+ totalEnergy += vel.vx * vel.vx + vel.vy * vel.vy;
54696
+ const moveX = vel.vx * dt;
54697
+ const moveY = vel.vy * dt;
54698
+ if (Math.abs(moveX) >= this.MIN_MOVE_PX || Math.abs(moveY) >= this.MIN_MOVE_PX) {
54699
+ item.transformation.applyMatrixSilent({
54700
+ translateX: moveX,
54701
+ translateY: moveY,
54702
+ scaleX: 1,
54703
+ scaleY: 1,
54704
+ shearX: 0,
54705
+ shearY: 0
54706
+ });
54707
+ }
54708
+ }
54709
+ if (totalEnergy < conf.FG_SLEEP_THRESHOLD && this.tickTimer !== null) {
54710
+ clearInterval(this.tickTimer);
54711
+ this.tickTimer = null;
54712
+ this.syncPositions();
54713
+ }
54714
+ }
54715
+ syncPositions() {
54716
+ const nodes = this.getNodes();
54717
+ if (nodes.length === 0)
54718
+ return;
54719
+ const movedItems = nodes.map((item) => {
54720
+ const id = item.getId();
54721
+ const pos = item.transformation.getTranslation();
54722
+ const last = this.lastSyncedPositions.get(id);
54723
+ const dx = last ? pos.x - last.x : 0;
54724
+ const dy = last ? pos.y - last.y : 0;
54725
+ this.lastSyncedPositions.set(id, { x: pos.x, y: pos.y });
54726
+ return { id, dx, dy };
54727
+ }).filter(({ dx, dy }) => Math.abs(dx) > 0.5 || Math.abs(dy) > 0.5);
54728
+ if (movedItems.length === 0)
54729
+ return;
54730
+ const operation = {
54731
+ class: "Transformation",
54732
+ method: "applyMatrix",
54733
+ items: movedItems.map(({ id, dx, dy }) => ({
54734
+ id,
54735
+ matrix: { translateX: dx, translateY: dy, scaleX: 1, scaleY: 1, shearX: 0, shearY: 0 }
54736
+ }))
54737
+ };
54738
+ this.board.events.emit(operation);
54739
+ }
54740
+ wake() {
54741
+ if (this.tickTimer === null && this.syncTimer !== null) {
54742
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54743
+ }
54744
+ }
54745
+ }
54746
+
54572
54747
  // src/Board.ts
54573
54748
  class Board {
54574
54749
  boardId;
@@ -55641,6 +55816,25 @@ class Board {
55641
55816
  isGravityEnabled() {
55642
55817
  return this.gravity !== null;
55643
55818
  }
55819
+ forceGraph = null;
55820
+ enableForceGraph() {
55821
+ if (this.forceGraph)
55822
+ return;
55823
+ this.forceGraph = new ForceGraphEngine(this);
55824
+ this.forceGraph.start();
55825
+ }
55826
+ disableForceGraph() {
55827
+ if (!this.forceGraph)
55828
+ return;
55829
+ this.forceGraph.stop();
55830
+ this.forceGraph = null;
55831
+ }
55832
+ isForceGraphEnabled() {
55833
+ return this.forceGraph !== null;
55834
+ }
55835
+ wakeForceGraph() {
55836
+ this.forceGraph?.wake();
55837
+ }
55644
55838
  }
55645
55839
  // src/Events/Merge.ts
55646
55840
  var import_slate36 = require("slate");
package/dist/cjs/node.js CHANGED
@@ -5712,6 +5712,11 @@ var conf = {
5712
5712
  DEFAULT_GAME_ITEM_DIMENSIONS: { width: 200, height: 200 },
5713
5713
  MAX_CARD_SIZE: 500,
5714
5714
  CONNECTOR_ITEM_OFFSET: 20,
5715
+ FG_SPRING_K: 3,
5716
+ FG_TARGET_GAP: 60,
5717
+ FG_REPULSION: 400000,
5718
+ FG_DAMPING: 0.8,
5719
+ FG_SLEEP_THRESHOLD: 0.5,
5715
5720
  GRAVITY_G: 80,
5716
5721
  GRAVITY_G_CENTER: 120,
5717
5722
  GRAVITY_DAMPING: 0.96,
@@ -57042,6 +57047,176 @@ class GravityEngine {
57042
57047
  }
57043
57048
  }
57044
57049
 
57050
+ // src/ForceGraph/ForceGraphEngine.ts
57051
+ var EXCLUDED_TYPES = new Set(["Connector", "Comment"]);
57052
+
57053
+ class ForceGraphEngine {
57054
+ board;
57055
+ velocities = new Map;
57056
+ tickTimer = null;
57057
+ syncTimer = null;
57058
+ lastSyncedPositions = new Map;
57059
+ TICK_MS = 33;
57060
+ SYNC_MS = 300;
57061
+ SOFTENING_SQ = 100 * 100;
57062
+ MIN_MOVE_PX = 0.05;
57063
+ constructor(board) {
57064
+ this.board = board;
57065
+ }
57066
+ start() {
57067
+ if (this.tickTimer !== null)
57068
+ return;
57069
+ for (const item of this.getNodes()) {
57070
+ const pos = item.transformation.getTranslation();
57071
+ this.lastSyncedPositions.set(item.getId(), { x: pos.x, y: pos.y });
57072
+ this.velocities.set(item.getId(), { vx: 0, vy: 0 });
57073
+ }
57074
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
57075
+ this.syncTimer = setInterval(() => this.syncPositions(), this.SYNC_MS);
57076
+ }
57077
+ stop() {
57078
+ if (this.tickTimer !== null) {
57079
+ clearInterval(this.tickTimer);
57080
+ this.tickTimer = null;
57081
+ }
57082
+ if (this.syncTimer !== null) {
57083
+ clearInterval(this.syncTimer);
57084
+ this.syncTimer = null;
57085
+ }
57086
+ this.syncPositions();
57087
+ this.velocities.clear();
57088
+ this.lastSyncedPositions.clear();
57089
+ }
57090
+ getNodes() {
57091
+ return this.board.items.listAll().filter((item) => !EXCLUDED_TYPES.has(item.itemType) && !item.transformation.isLocked);
57092
+ }
57093
+ getConnectors() {
57094
+ return this.board.items.listAll().filter((item) => item.itemType === "Connector");
57095
+ }
57096
+ tick() {
57097
+ const dt = this.TICK_MS / 1000;
57098
+ const nodes = this.getNodes();
57099
+ if (nodes.length < 2)
57100
+ return;
57101
+ const snapMap = new Map;
57102
+ for (const item of nodes) {
57103
+ const pos = item.transformation.getTranslation();
57104
+ const mbr = item.getMbr();
57105
+ const w = Math.max(mbr.getWidth(), 1);
57106
+ const h2 = Math.max(mbr.getHeight(), 1);
57107
+ snapMap.set(item.getId(), {
57108
+ id: item.getId(),
57109
+ cx: pos.x + w * 0.5,
57110
+ cy: pos.y + h2 * 0.5,
57111
+ w,
57112
+ h: h2
57113
+ });
57114
+ }
57115
+ const snap = Array.from(snapMap.values());
57116
+ const ax = new Map;
57117
+ const ay = new Map;
57118
+ for (const s2 of snap) {
57119
+ ax.set(s2.id, 0);
57120
+ ay.set(s2.id, 0);
57121
+ }
57122
+ for (const connector of this.getConnectors()) {
57123
+ const { startItem, endItem } = connector.getConnectedItems();
57124
+ if (!startItem || !endItem)
57125
+ continue;
57126
+ const s1 = snapMap.get(startItem.getId());
57127
+ const s2 = snapMap.get(endItem.getId());
57128
+ if (!s1 || !s2)
57129
+ continue;
57130
+ const dx = s2.cx - s1.cx;
57131
+ const dy = s2.cy - s1.cy;
57132
+ const dist = Math.sqrt(dx * dx + dy * dy) + 0.001;
57133
+ const targetDist = (Math.max(s1.w, s1.h) + Math.max(s2.w, s2.h)) * 0.5 + conf.FG_TARGET_GAP;
57134
+ const stretch = dist - targetDist;
57135
+ const forceMag = stretch * conf.FG_SPRING_K;
57136
+ const fx = dx / dist * forceMag;
57137
+ const fy = dy / dist * forceMag;
57138
+ ax.set(s1.id, (ax.get(s1.id) ?? 0) + fx);
57139
+ ay.set(s1.id, (ay.get(s1.id) ?? 0) + fy);
57140
+ ax.set(s2.id, (ax.get(s2.id) ?? 0) - fx);
57141
+ ay.set(s2.id, (ay.get(s2.id) ?? 0) - fy);
57142
+ }
57143
+ for (let i = 0;i < snap.length; i++) {
57144
+ for (let j = i + 1;j < snap.length; j++) {
57145
+ const s1 = snap[i];
57146
+ const s2 = snap[j];
57147
+ const dx = s2.cx - s1.cx;
57148
+ const dy = s2.cy - s1.cy;
57149
+ const distSq = dx * dx + dy * dy + this.SOFTENING_SQ;
57150
+ const repMag = conf.FG_REPULSION / distSq;
57151
+ const fx = dx * repMag;
57152
+ const fy = dy * repMag;
57153
+ ax.set(s1.id, (ax.get(s1.id) ?? 0) - fx);
57154
+ ay.set(s1.id, (ay.get(s1.id) ?? 0) - fy);
57155
+ ax.set(s2.id, (ax.get(s2.id) ?? 0) + fx);
57156
+ ay.set(s2.id, (ay.get(s2.id) ?? 0) + fy);
57157
+ }
57158
+ }
57159
+ let totalEnergy = 0;
57160
+ for (const item of nodes) {
57161
+ const id = item.getId();
57162
+ if (!this.velocities.has(id)) {
57163
+ this.velocities.set(id, { vx: 0, vy: 0 });
57164
+ }
57165
+ const vel = this.velocities.get(id);
57166
+ vel.vx = (vel.vx + (ax.get(id) ?? 0) * dt) * conf.FG_DAMPING;
57167
+ vel.vy = (vel.vy + (ay.get(id) ?? 0) * dt) * conf.FG_DAMPING;
57168
+ totalEnergy += vel.vx * vel.vx + vel.vy * vel.vy;
57169
+ const moveX = vel.vx * dt;
57170
+ const moveY = vel.vy * dt;
57171
+ if (Math.abs(moveX) >= this.MIN_MOVE_PX || Math.abs(moveY) >= this.MIN_MOVE_PX) {
57172
+ item.transformation.applyMatrixSilent({
57173
+ translateX: moveX,
57174
+ translateY: moveY,
57175
+ scaleX: 1,
57176
+ scaleY: 1,
57177
+ shearX: 0,
57178
+ shearY: 0
57179
+ });
57180
+ }
57181
+ }
57182
+ if (totalEnergy < conf.FG_SLEEP_THRESHOLD && this.tickTimer !== null) {
57183
+ clearInterval(this.tickTimer);
57184
+ this.tickTimer = null;
57185
+ this.syncPositions();
57186
+ }
57187
+ }
57188
+ syncPositions() {
57189
+ const nodes = this.getNodes();
57190
+ if (nodes.length === 0)
57191
+ return;
57192
+ const movedItems = nodes.map((item) => {
57193
+ const id = item.getId();
57194
+ const pos = item.transformation.getTranslation();
57195
+ const last = this.lastSyncedPositions.get(id);
57196
+ const dx = last ? pos.x - last.x : 0;
57197
+ const dy = last ? pos.y - last.y : 0;
57198
+ this.lastSyncedPositions.set(id, { x: pos.x, y: pos.y });
57199
+ return { id, dx, dy };
57200
+ }).filter(({ dx, dy }) => Math.abs(dx) > 0.5 || Math.abs(dy) > 0.5);
57201
+ if (movedItems.length === 0)
57202
+ return;
57203
+ const operation = {
57204
+ class: "Transformation",
57205
+ method: "applyMatrix",
57206
+ items: movedItems.map(({ id, dx, dy }) => ({
57207
+ id,
57208
+ matrix: { translateX: dx, translateY: dy, scaleX: 1, scaleY: 1, shearX: 0, shearY: 0 }
57209
+ }))
57210
+ };
57211
+ this.board.events.emit(operation);
57212
+ }
57213
+ wake() {
57214
+ if (this.tickTimer === null && this.syncTimer !== null) {
57215
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
57216
+ }
57217
+ }
57218
+ }
57219
+
57045
57220
  // src/Board.ts
57046
57221
  class Board {
57047
57222
  boardId;
@@ -58114,6 +58289,25 @@ class Board {
58114
58289
  isGravityEnabled() {
58115
58290
  return this.gravity !== null;
58116
58291
  }
58292
+ forceGraph = null;
58293
+ enableForceGraph() {
58294
+ if (this.forceGraph)
58295
+ return;
58296
+ this.forceGraph = new ForceGraphEngine(this);
58297
+ this.forceGraph.start();
58298
+ }
58299
+ disableForceGraph() {
58300
+ if (!this.forceGraph)
58301
+ return;
58302
+ this.forceGraph.stop();
58303
+ this.forceGraph = null;
58304
+ }
58305
+ isForceGraphEnabled() {
58306
+ return this.forceGraph !== null;
58307
+ }
58308
+ wakeForceGraph() {
58309
+ this.forceGraph?.wake();
58310
+ }
58117
58311
  }
58118
58312
  // src/Events/Merge.ts
58119
58313
  var import_slate35 = require("slate");
@@ -4495,6 +4495,11 @@ var conf = {
4495
4495
  DEFAULT_GAME_ITEM_DIMENSIONS: { width: 200, height: 200 },
4496
4496
  MAX_CARD_SIZE: 500,
4497
4497
  CONNECTOR_ITEM_OFFSET: 20,
4498
+ FG_SPRING_K: 3,
4499
+ FG_TARGET_GAP: 60,
4500
+ FG_REPULSION: 400000,
4501
+ FG_DAMPING: 0.8,
4502
+ FG_SLEEP_THRESHOLD: 0.5,
4498
4503
  GRAVITY_G: 80,
4499
4504
  GRAVITY_G_CENTER: 120,
4500
4505
  GRAVITY_DAMPING: 0.96,
@@ -54398,6 +54403,176 @@ class GravityEngine {
54398
54403
  }
54399
54404
  }
54400
54405
 
54406
+ // src/ForceGraph/ForceGraphEngine.ts
54407
+ var EXCLUDED_TYPES = new Set(["Connector", "Comment"]);
54408
+
54409
+ class ForceGraphEngine {
54410
+ board;
54411
+ velocities = new Map;
54412
+ tickTimer = null;
54413
+ syncTimer = null;
54414
+ lastSyncedPositions = new Map;
54415
+ TICK_MS = 33;
54416
+ SYNC_MS = 300;
54417
+ SOFTENING_SQ = 100 * 100;
54418
+ MIN_MOVE_PX = 0.05;
54419
+ constructor(board) {
54420
+ this.board = board;
54421
+ }
54422
+ start() {
54423
+ if (this.tickTimer !== null)
54424
+ return;
54425
+ for (const item of this.getNodes()) {
54426
+ const pos = item.transformation.getTranslation();
54427
+ this.lastSyncedPositions.set(item.getId(), { x: pos.x, y: pos.y });
54428
+ this.velocities.set(item.getId(), { vx: 0, vy: 0 });
54429
+ }
54430
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54431
+ this.syncTimer = setInterval(() => this.syncPositions(), this.SYNC_MS);
54432
+ }
54433
+ stop() {
54434
+ if (this.tickTimer !== null) {
54435
+ clearInterval(this.tickTimer);
54436
+ this.tickTimer = null;
54437
+ }
54438
+ if (this.syncTimer !== null) {
54439
+ clearInterval(this.syncTimer);
54440
+ this.syncTimer = null;
54441
+ }
54442
+ this.syncPositions();
54443
+ this.velocities.clear();
54444
+ this.lastSyncedPositions.clear();
54445
+ }
54446
+ getNodes() {
54447
+ return this.board.items.listAll().filter((item) => !EXCLUDED_TYPES.has(item.itemType) && !item.transformation.isLocked);
54448
+ }
54449
+ getConnectors() {
54450
+ return this.board.items.listAll().filter((item) => item.itemType === "Connector");
54451
+ }
54452
+ tick() {
54453
+ const dt = this.TICK_MS / 1000;
54454
+ const nodes = this.getNodes();
54455
+ if (nodes.length < 2)
54456
+ return;
54457
+ const snapMap = new Map;
54458
+ for (const item of nodes) {
54459
+ const pos = item.transformation.getTranslation();
54460
+ const mbr = item.getMbr();
54461
+ const w = Math.max(mbr.getWidth(), 1);
54462
+ const h2 = Math.max(mbr.getHeight(), 1);
54463
+ snapMap.set(item.getId(), {
54464
+ id: item.getId(),
54465
+ cx: pos.x + w * 0.5,
54466
+ cy: pos.y + h2 * 0.5,
54467
+ w,
54468
+ h: h2
54469
+ });
54470
+ }
54471
+ const snap = Array.from(snapMap.values());
54472
+ const ax = new Map;
54473
+ const ay = new Map;
54474
+ for (const s2 of snap) {
54475
+ ax.set(s2.id, 0);
54476
+ ay.set(s2.id, 0);
54477
+ }
54478
+ for (const connector of this.getConnectors()) {
54479
+ const { startItem, endItem } = connector.getConnectedItems();
54480
+ if (!startItem || !endItem)
54481
+ continue;
54482
+ const s1 = snapMap.get(startItem.getId());
54483
+ const s2 = snapMap.get(endItem.getId());
54484
+ if (!s1 || !s2)
54485
+ continue;
54486
+ const dx = s2.cx - s1.cx;
54487
+ const dy = s2.cy - s1.cy;
54488
+ const dist = Math.sqrt(dx * dx + dy * dy) + 0.001;
54489
+ const targetDist = (Math.max(s1.w, s1.h) + Math.max(s2.w, s2.h)) * 0.5 + conf.FG_TARGET_GAP;
54490
+ const stretch = dist - targetDist;
54491
+ const forceMag = stretch * conf.FG_SPRING_K;
54492
+ const fx = dx / dist * forceMag;
54493
+ const fy = dy / dist * forceMag;
54494
+ ax.set(s1.id, (ax.get(s1.id) ?? 0) + fx);
54495
+ ay.set(s1.id, (ay.get(s1.id) ?? 0) + fy);
54496
+ ax.set(s2.id, (ax.get(s2.id) ?? 0) - fx);
54497
+ ay.set(s2.id, (ay.get(s2.id) ?? 0) - fy);
54498
+ }
54499
+ for (let i = 0;i < snap.length; i++) {
54500
+ for (let j = i + 1;j < snap.length; j++) {
54501
+ const s1 = snap[i];
54502
+ const s2 = snap[j];
54503
+ const dx = s2.cx - s1.cx;
54504
+ const dy = s2.cy - s1.cy;
54505
+ const distSq = dx * dx + dy * dy + this.SOFTENING_SQ;
54506
+ const repMag = conf.FG_REPULSION / distSq;
54507
+ const fx = dx * repMag;
54508
+ const fy = dy * repMag;
54509
+ ax.set(s1.id, (ax.get(s1.id) ?? 0) - fx);
54510
+ ay.set(s1.id, (ay.get(s1.id) ?? 0) - fy);
54511
+ ax.set(s2.id, (ax.get(s2.id) ?? 0) + fx);
54512
+ ay.set(s2.id, (ay.get(s2.id) ?? 0) + fy);
54513
+ }
54514
+ }
54515
+ let totalEnergy = 0;
54516
+ for (const item of nodes) {
54517
+ const id = item.getId();
54518
+ if (!this.velocities.has(id)) {
54519
+ this.velocities.set(id, { vx: 0, vy: 0 });
54520
+ }
54521
+ const vel = this.velocities.get(id);
54522
+ vel.vx = (vel.vx + (ax.get(id) ?? 0) * dt) * conf.FG_DAMPING;
54523
+ vel.vy = (vel.vy + (ay.get(id) ?? 0) * dt) * conf.FG_DAMPING;
54524
+ totalEnergy += vel.vx * vel.vx + vel.vy * vel.vy;
54525
+ const moveX = vel.vx * dt;
54526
+ const moveY = vel.vy * dt;
54527
+ if (Math.abs(moveX) >= this.MIN_MOVE_PX || Math.abs(moveY) >= this.MIN_MOVE_PX) {
54528
+ item.transformation.applyMatrixSilent({
54529
+ translateX: moveX,
54530
+ translateY: moveY,
54531
+ scaleX: 1,
54532
+ scaleY: 1,
54533
+ shearX: 0,
54534
+ shearY: 0
54535
+ });
54536
+ }
54537
+ }
54538
+ if (totalEnergy < conf.FG_SLEEP_THRESHOLD && this.tickTimer !== null) {
54539
+ clearInterval(this.tickTimer);
54540
+ this.tickTimer = null;
54541
+ this.syncPositions();
54542
+ }
54543
+ }
54544
+ syncPositions() {
54545
+ const nodes = this.getNodes();
54546
+ if (nodes.length === 0)
54547
+ return;
54548
+ const movedItems = nodes.map((item) => {
54549
+ const id = item.getId();
54550
+ const pos = item.transformation.getTranslation();
54551
+ const last = this.lastSyncedPositions.get(id);
54552
+ const dx = last ? pos.x - last.x : 0;
54553
+ const dy = last ? pos.y - last.y : 0;
54554
+ this.lastSyncedPositions.set(id, { x: pos.x, y: pos.y });
54555
+ return { id, dx, dy };
54556
+ }).filter(({ dx, dy }) => Math.abs(dx) > 0.5 || Math.abs(dy) > 0.5);
54557
+ if (movedItems.length === 0)
54558
+ return;
54559
+ const operation = {
54560
+ class: "Transformation",
54561
+ method: "applyMatrix",
54562
+ items: movedItems.map(({ id, dx, dy }) => ({
54563
+ id,
54564
+ matrix: { translateX: dx, translateY: dy, scaleX: 1, scaleY: 1, shearX: 0, shearY: 0 }
54565
+ }))
54566
+ };
54567
+ this.board.events.emit(operation);
54568
+ }
54569
+ wake() {
54570
+ if (this.tickTimer === null && this.syncTimer !== null) {
54571
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54572
+ }
54573
+ }
54574
+ }
54575
+
54401
54576
  // src/Board.ts
54402
54577
  class Board {
54403
54578
  boardId;
@@ -55470,6 +55645,25 @@ class Board {
55470
55645
  isGravityEnabled() {
55471
55646
  return this.gravity !== null;
55472
55647
  }
55648
+ forceGraph = null;
55649
+ enableForceGraph() {
55650
+ if (this.forceGraph)
55651
+ return;
55652
+ this.forceGraph = new ForceGraphEngine(this);
55653
+ this.forceGraph.start();
55654
+ }
55655
+ disableForceGraph() {
55656
+ if (!this.forceGraph)
55657
+ return;
55658
+ this.forceGraph.stop();
55659
+ this.forceGraph = null;
55660
+ }
55661
+ isForceGraphEnabled() {
55662
+ return this.forceGraph !== null;
55663
+ }
55664
+ wakeForceGraph() {
55665
+ this.forceGraph?.wake();
55666
+ }
55473
55667
  }
55474
55668
  // src/Events/Merge.ts
55475
55669
  import { Path as Path15 } from "slate";
package/dist/esm/index.js CHANGED
@@ -4488,6 +4488,11 @@ var conf = {
4488
4488
  DEFAULT_GAME_ITEM_DIMENSIONS: { width: 200, height: 200 },
4489
4489
  MAX_CARD_SIZE: 500,
4490
4490
  CONNECTOR_ITEM_OFFSET: 20,
4491
+ FG_SPRING_K: 3,
4492
+ FG_TARGET_GAP: 60,
4493
+ FG_REPULSION: 400000,
4494
+ FG_DAMPING: 0.8,
4495
+ FG_SLEEP_THRESHOLD: 0.5,
4491
4496
  GRAVITY_G: 80,
4492
4497
  GRAVITY_G_CENTER: 120,
4493
4498
  GRAVITY_DAMPING: 0.96,
@@ -54391,6 +54396,176 @@ class GravityEngine {
54391
54396
  }
54392
54397
  }
54393
54398
 
54399
+ // src/ForceGraph/ForceGraphEngine.ts
54400
+ var EXCLUDED_TYPES = new Set(["Connector", "Comment"]);
54401
+
54402
+ class ForceGraphEngine {
54403
+ board;
54404
+ velocities = new Map;
54405
+ tickTimer = null;
54406
+ syncTimer = null;
54407
+ lastSyncedPositions = new Map;
54408
+ TICK_MS = 33;
54409
+ SYNC_MS = 300;
54410
+ SOFTENING_SQ = 100 * 100;
54411
+ MIN_MOVE_PX = 0.05;
54412
+ constructor(board) {
54413
+ this.board = board;
54414
+ }
54415
+ start() {
54416
+ if (this.tickTimer !== null)
54417
+ return;
54418
+ for (const item of this.getNodes()) {
54419
+ const pos = item.transformation.getTranslation();
54420
+ this.lastSyncedPositions.set(item.getId(), { x: pos.x, y: pos.y });
54421
+ this.velocities.set(item.getId(), { vx: 0, vy: 0 });
54422
+ }
54423
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54424
+ this.syncTimer = setInterval(() => this.syncPositions(), this.SYNC_MS);
54425
+ }
54426
+ stop() {
54427
+ if (this.tickTimer !== null) {
54428
+ clearInterval(this.tickTimer);
54429
+ this.tickTimer = null;
54430
+ }
54431
+ if (this.syncTimer !== null) {
54432
+ clearInterval(this.syncTimer);
54433
+ this.syncTimer = null;
54434
+ }
54435
+ this.syncPositions();
54436
+ this.velocities.clear();
54437
+ this.lastSyncedPositions.clear();
54438
+ }
54439
+ getNodes() {
54440
+ return this.board.items.listAll().filter((item) => !EXCLUDED_TYPES.has(item.itemType) && !item.transformation.isLocked);
54441
+ }
54442
+ getConnectors() {
54443
+ return this.board.items.listAll().filter((item) => item.itemType === "Connector");
54444
+ }
54445
+ tick() {
54446
+ const dt = this.TICK_MS / 1000;
54447
+ const nodes = this.getNodes();
54448
+ if (nodes.length < 2)
54449
+ return;
54450
+ const snapMap = new Map;
54451
+ for (const item of nodes) {
54452
+ const pos = item.transformation.getTranslation();
54453
+ const mbr = item.getMbr();
54454
+ const w = Math.max(mbr.getWidth(), 1);
54455
+ const h2 = Math.max(mbr.getHeight(), 1);
54456
+ snapMap.set(item.getId(), {
54457
+ id: item.getId(),
54458
+ cx: pos.x + w * 0.5,
54459
+ cy: pos.y + h2 * 0.5,
54460
+ w,
54461
+ h: h2
54462
+ });
54463
+ }
54464
+ const snap = Array.from(snapMap.values());
54465
+ const ax = new Map;
54466
+ const ay = new Map;
54467
+ for (const s2 of snap) {
54468
+ ax.set(s2.id, 0);
54469
+ ay.set(s2.id, 0);
54470
+ }
54471
+ for (const connector of this.getConnectors()) {
54472
+ const { startItem, endItem } = connector.getConnectedItems();
54473
+ if (!startItem || !endItem)
54474
+ continue;
54475
+ const s1 = snapMap.get(startItem.getId());
54476
+ const s2 = snapMap.get(endItem.getId());
54477
+ if (!s1 || !s2)
54478
+ continue;
54479
+ const dx = s2.cx - s1.cx;
54480
+ const dy = s2.cy - s1.cy;
54481
+ const dist = Math.sqrt(dx * dx + dy * dy) + 0.001;
54482
+ const targetDist = (Math.max(s1.w, s1.h) + Math.max(s2.w, s2.h)) * 0.5 + conf.FG_TARGET_GAP;
54483
+ const stretch = dist - targetDist;
54484
+ const forceMag = stretch * conf.FG_SPRING_K;
54485
+ const fx = dx / dist * forceMag;
54486
+ const fy = dy / dist * forceMag;
54487
+ ax.set(s1.id, (ax.get(s1.id) ?? 0) + fx);
54488
+ ay.set(s1.id, (ay.get(s1.id) ?? 0) + fy);
54489
+ ax.set(s2.id, (ax.get(s2.id) ?? 0) - fx);
54490
+ ay.set(s2.id, (ay.get(s2.id) ?? 0) - fy);
54491
+ }
54492
+ for (let i = 0;i < snap.length; i++) {
54493
+ for (let j = i + 1;j < snap.length; j++) {
54494
+ const s1 = snap[i];
54495
+ const s2 = snap[j];
54496
+ const dx = s2.cx - s1.cx;
54497
+ const dy = s2.cy - s1.cy;
54498
+ const distSq = dx * dx + dy * dy + this.SOFTENING_SQ;
54499
+ const repMag = conf.FG_REPULSION / distSq;
54500
+ const fx = dx * repMag;
54501
+ const fy = dy * repMag;
54502
+ ax.set(s1.id, (ax.get(s1.id) ?? 0) - fx);
54503
+ ay.set(s1.id, (ay.get(s1.id) ?? 0) - fy);
54504
+ ax.set(s2.id, (ax.get(s2.id) ?? 0) + fx);
54505
+ ay.set(s2.id, (ay.get(s2.id) ?? 0) + fy);
54506
+ }
54507
+ }
54508
+ let totalEnergy = 0;
54509
+ for (const item of nodes) {
54510
+ const id = item.getId();
54511
+ if (!this.velocities.has(id)) {
54512
+ this.velocities.set(id, { vx: 0, vy: 0 });
54513
+ }
54514
+ const vel = this.velocities.get(id);
54515
+ vel.vx = (vel.vx + (ax.get(id) ?? 0) * dt) * conf.FG_DAMPING;
54516
+ vel.vy = (vel.vy + (ay.get(id) ?? 0) * dt) * conf.FG_DAMPING;
54517
+ totalEnergy += vel.vx * vel.vx + vel.vy * vel.vy;
54518
+ const moveX = vel.vx * dt;
54519
+ const moveY = vel.vy * dt;
54520
+ if (Math.abs(moveX) >= this.MIN_MOVE_PX || Math.abs(moveY) >= this.MIN_MOVE_PX) {
54521
+ item.transformation.applyMatrixSilent({
54522
+ translateX: moveX,
54523
+ translateY: moveY,
54524
+ scaleX: 1,
54525
+ scaleY: 1,
54526
+ shearX: 0,
54527
+ shearY: 0
54528
+ });
54529
+ }
54530
+ }
54531
+ if (totalEnergy < conf.FG_SLEEP_THRESHOLD && this.tickTimer !== null) {
54532
+ clearInterval(this.tickTimer);
54533
+ this.tickTimer = null;
54534
+ this.syncPositions();
54535
+ }
54536
+ }
54537
+ syncPositions() {
54538
+ const nodes = this.getNodes();
54539
+ if (nodes.length === 0)
54540
+ return;
54541
+ const movedItems = nodes.map((item) => {
54542
+ const id = item.getId();
54543
+ const pos = item.transformation.getTranslation();
54544
+ const last = this.lastSyncedPositions.get(id);
54545
+ const dx = last ? pos.x - last.x : 0;
54546
+ const dy = last ? pos.y - last.y : 0;
54547
+ this.lastSyncedPositions.set(id, { x: pos.x, y: pos.y });
54548
+ return { id, dx, dy };
54549
+ }).filter(({ dx, dy }) => Math.abs(dx) > 0.5 || Math.abs(dy) > 0.5);
54550
+ if (movedItems.length === 0)
54551
+ return;
54552
+ const operation = {
54553
+ class: "Transformation",
54554
+ method: "applyMatrix",
54555
+ items: movedItems.map(({ id, dx, dy }) => ({
54556
+ id,
54557
+ matrix: { translateX: dx, translateY: dy, scaleX: 1, scaleY: 1, shearX: 0, shearY: 0 }
54558
+ }))
54559
+ };
54560
+ this.board.events.emit(operation);
54561
+ }
54562
+ wake() {
54563
+ if (this.tickTimer === null && this.syncTimer !== null) {
54564
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54565
+ }
54566
+ }
54567
+ }
54568
+
54394
54569
  // src/Board.ts
54395
54570
  class Board {
54396
54571
  boardId;
@@ -55463,6 +55638,25 @@ class Board {
55463
55638
  isGravityEnabled() {
55464
55639
  return this.gravity !== null;
55465
55640
  }
55641
+ forceGraph = null;
55642
+ enableForceGraph() {
55643
+ if (this.forceGraph)
55644
+ return;
55645
+ this.forceGraph = new ForceGraphEngine(this);
55646
+ this.forceGraph.start();
55647
+ }
55648
+ disableForceGraph() {
55649
+ if (!this.forceGraph)
55650
+ return;
55651
+ this.forceGraph.stop();
55652
+ this.forceGraph = null;
55653
+ }
55654
+ isForceGraphEnabled() {
55655
+ return this.forceGraph !== null;
55656
+ }
55657
+ wakeForceGraph() {
55658
+ this.forceGraph?.wake();
55659
+ }
55466
55660
  }
55467
55661
  // src/Events/Merge.ts
55468
55662
  import { Path as Path15 } from "slate";
package/dist/esm/node.js CHANGED
@@ -5272,6 +5272,11 @@ var conf = {
5272
5272
  DEFAULT_GAME_ITEM_DIMENSIONS: { width: 200, height: 200 },
5273
5273
  MAX_CARD_SIZE: 500,
5274
5274
  CONNECTOR_ITEM_OFFSET: 20,
5275
+ FG_SPRING_K: 3,
5276
+ FG_TARGET_GAP: 60,
5277
+ FG_REPULSION: 400000,
5278
+ FG_DAMPING: 0.8,
5279
+ FG_SLEEP_THRESHOLD: 0.5,
5275
5280
  GRAVITY_G: 80,
5276
5281
  GRAVITY_G_CENTER: 120,
5277
5282
  GRAVITY_DAMPING: 0.96,
@@ -56859,6 +56864,176 @@ class GravityEngine {
56859
56864
  }
56860
56865
  }
56861
56866
 
56867
+ // src/ForceGraph/ForceGraphEngine.ts
56868
+ var EXCLUDED_TYPES = new Set(["Connector", "Comment"]);
56869
+
56870
+ class ForceGraphEngine {
56871
+ board;
56872
+ velocities = new Map;
56873
+ tickTimer = null;
56874
+ syncTimer = null;
56875
+ lastSyncedPositions = new Map;
56876
+ TICK_MS = 33;
56877
+ SYNC_MS = 300;
56878
+ SOFTENING_SQ = 100 * 100;
56879
+ MIN_MOVE_PX = 0.05;
56880
+ constructor(board) {
56881
+ this.board = board;
56882
+ }
56883
+ start() {
56884
+ if (this.tickTimer !== null)
56885
+ return;
56886
+ for (const item of this.getNodes()) {
56887
+ const pos = item.transformation.getTranslation();
56888
+ this.lastSyncedPositions.set(item.getId(), { x: pos.x, y: pos.y });
56889
+ this.velocities.set(item.getId(), { vx: 0, vy: 0 });
56890
+ }
56891
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
56892
+ this.syncTimer = setInterval(() => this.syncPositions(), this.SYNC_MS);
56893
+ }
56894
+ stop() {
56895
+ if (this.tickTimer !== null) {
56896
+ clearInterval(this.tickTimer);
56897
+ this.tickTimer = null;
56898
+ }
56899
+ if (this.syncTimer !== null) {
56900
+ clearInterval(this.syncTimer);
56901
+ this.syncTimer = null;
56902
+ }
56903
+ this.syncPositions();
56904
+ this.velocities.clear();
56905
+ this.lastSyncedPositions.clear();
56906
+ }
56907
+ getNodes() {
56908
+ return this.board.items.listAll().filter((item) => !EXCLUDED_TYPES.has(item.itemType) && !item.transformation.isLocked);
56909
+ }
56910
+ getConnectors() {
56911
+ return this.board.items.listAll().filter((item) => item.itemType === "Connector");
56912
+ }
56913
+ tick() {
56914
+ const dt = this.TICK_MS / 1000;
56915
+ const nodes = this.getNodes();
56916
+ if (nodes.length < 2)
56917
+ return;
56918
+ const snapMap = new Map;
56919
+ for (const item of nodes) {
56920
+ const pos = item.transformation.getTranslation();
56921
+ const mbr = item.getMbr();
56922
+ const w = Math.max(mbr.getWidth(), 1);
56923
+ const h2 = Math.max(mbr.getHeight(), 1);
56924
+ snapMap.set(item.getId(), {
56925
+ id: item.getId(),
56926
+ cx: pos.x + w * 0.5,
56927
+ cy: pos.y + h2 * 0.5,
56928
+ w,
56929
+ h: h2
56930
+ });
56931
+ }
56932
+ const snap = Array.from(snapMap.values());
56933
+ const ax = new Map;
56934
+ const ay = new Map;
56935
+ for (const s2 of snap) {
56936
+ ax.set(s2.id, 0);
56937
+ ay.set(s2.id, 0);
56938
+ }
56939
+ for (const connector of this.getConnectors()) {
56940
+ const { startItem, endItem } = connector.getConnectedItems();
56941
+ if (!startItem || !endItem)
56942
+ continue;
56943
+ const s1 = snapMap.get(startItem.getId());
56944
+ const s2 = snapMap.get(endItem.getId());
56945
+ if (!s1 || !s2)
56946
+ continue;
56947
+ const dx = s2.cx - s1.cx;
56948
+ const dy = s2.cy - s1.cy;
56949
+ const dist = Math.sqrt(dx * dx + dy * dy) + 0.001;
56950
+ const targetDist = (Math.max(s1.w, s1.h) + Math.max(s2.w, s2.h)) * 0.5 + conf.FG_TARGET_GAP;
56951
+ const stretch = dist - targetDist;
56952
+ const forceMag = stretch * conf.FG_SPRING_K;
56953
+ const fx = dx / dist * forceMag;
56954
+ const fy = dy / dist * forceMag;
56955
+ ax.set(s1.id, (ax.get(s1.id) ?? 0) + fx);
56956
+ ay.set(s1.id, (ay.get(s1.id) ?? 0) + fy);
56957
+ ax.set(s2.id, (ax.get(s2.id) ?? 0) - fx);
56958
+ ay.set(s2.id, (ay.get(s2.id) ?? 0) - fy);
56959
+ }
56960
+ for (let i = 0;i < snap.length; i++) {
56961
+ for (let j = i + 1;j < snap.length; j++) {
56962
+ const s1 = snap[i];
56963
+ const s2 = snap[j];
56964
+ const dx = s2.cx - s1.cx;
56965
+ const dy = s2.cy - s1.cy;
56966
+ const distSq = dx * dx + dy * dy + this.SOFTENING_SQ;
56967
+ const repMag = conf.FG_REPULSION / distSq;
56968
+ const fx = dx * repMag;
56969
+ const fy = dy * repMag;
56970
+ ax.set(s1.id, (ax.get(s1.id) ?? 0) - fx);
56971
+ ay.set(s1.id, (ay.get(s1.id) ?? 0) - fy);
56972
+ ax.set(s2.id, (ax.get(s2.id) ?? 0) + fx);
56973
+ ay.set(s2.id, (ay.get(s2.id) ?? 0) + fy);
56974
+ }
56975
+ }
56976
+ let totalEnergy = 0;
56977
+ for (const item of nodes) {
56978
+ const id = item.getId();
56979
+ if (!this.velocities.has(id)) {
56980
+ this.velocities.set(id, { vx: 0, vy: 0 });
56981
+ }
56982
+ const vel = this.velocities.get(id);
56983
+ vel.vx = (vel.vx + (ax.get(id) ?? 0) * dt) * conf.FG_DAMPING;
56984
+ vel.vy = (vel.vy + (ay.get(id) ?? 0) * dt) * conf.FG_DAMPING;
56985
+ totalEnergy += vel.vx * vel.vx + vel.vy * vel.vy;
56986
+ const moveX = vel.vx * dt;
56987
+ const moveY = vel.vy * dt;
56988
+ if (Math.abs(moveX) >= this.MIN_MOVE_PX || Math.abs(moveY) >= this.MIN_MOVE_PX) {
56989
+ item.transformation.applyMatrixSilent({
56990
+ translateX: moveX,
56991
+ translateY: moveY,
56992
+ scaleX: 1,
56993
+ scaleY: 1,
56994
+ shearX: 0,
56995
+ shearY: 0
56996
+ });
56997
+ }
56998
+ }
56999
+ if (totalEnergy < conf.FG_SLEEP_THRESHOLD && this.tickTimer !== null) {
57000
+ clearInterval(this.tickTimer);
57001
+ this.tickTimer = null;
57002
+ this.syncPositions();
57003
+ }
57004
+ }
57005
+ syncPositions() {
57006
+ const nodes = this.getNodes();
57007
+ if (nodes.length === 0)
57008
+ return;
57009
+ const movedItems = nodes.map((item) => {
57010
+ const id = item.getId();
57011
+ const pos = item.transformation.getTranslation();
57012
+ const last = this.lastSyncedPositions.get(id);
57013
+ const dx = last ? pos.x - last.x : 0;
57014
+ const dy = last ? pos.y - last.y : 0;
57015
+ this.lastSyncedPositions.set(id, { x: pos.x, y: pos.y });
57016
+ return { id, dx, dy };
57017
+ }).filter(({ dx, dy }) => Math.abs(dx) > 0.5 || Math.abs(dy) > 0.5);
57018
+ if (movedItems.length === 0)
57019
+ return;
57020
+ const operation = {
57021
+ class: "Transformation",
57022
+ method: "applyMatrix",
57023
+ items: movedItems.map(({ id, dx, dy }) => ({
57024
+ id,
57025
+ matrix: { translateX: dx, translateY: dy, scaleX: 1, scaleY: 1, shearX: 0, shearY: 0 }
57026
+ }))
57027
+ };
57028
+ this.board.events.emit(operation);
57029
+ }
57030
+ wake() {
57031
+ if (this.tickTimer === null && this.syncTimer !== null) {
57032
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
57033
+ }
57034
+ }
57035
+ }
57036
+
56862
57037
  // src/Board.ts
56863
57038
  class Board {
56864
57039
  boardId;
@@ -57931,6 +58106,25 @@ class Board {
57931
58106
  isGravityEnabled() {
57932
58107
  return this.gravity !== null;
57933
58108
  }
58109
+ forceGraph = null;
58110
+ enableForceGraph() {
58111
+ if (this.forceGraph)
58112
+ return;
58113
+ this.forceGraph = new ForceGraphEngine(this);
58114
+ this.forceGraph.start();
58115
+ }
58116
+ disableForceGraph() {
58117
+ if (!this.forceGraph)
58118
+ return;
58119
+ this.forceGraph.stop();
58120
+ this.forceGraph = null;
58121
+ }
58122
+ isForceGraphEnabled() {
58123
+ return this.forceGraph !== null;
58124
+ }
58125
+ wakeForceGraph() {
58126
+ this.forceGraph?.wake();
58127
+ }
57934
58128
  }
57935
58129
  // src/Events/Merge.ts
57936
58130
  import { Path as Path14 } from "slate";
@@ -139,6 +139,12 @@ export declare class Board {
139
139
  enableGravity(): void;
140
140
  disableGravity(): void;
141
141
  isGravityEnabled(): boolean;
142
+ private forceGraph;
143
+ enableForceGraph(): void;
144
+ disableForceGraph(): void;
145
+ isForceGraphEnabled(): boolean;
146
+ /** Call after dragging a node to re-wake the physics engine if it was sleeping. */
147
+ wakeForceGraph(): void;
142
148
  }
143
149
  export interface BoardSnapshot {
144
150
  items: (ItemData & {
@@ -0,0 +1,21 @@
1
+ import { Board } from '../Board';
2
+ export declare class ForceGraphEngine {
3
+ private board;
4
+ private velocities;
5
+ private tickTimer;
6
+ private syncTimer;
7
+ private lastSyncedPositions;
8
+ private readonly TICK_MS;
9
+ private readonly SYNC_MS;
10
+ private readonly SOFTENING_SQ;
11
+ private readonly MIN_MOVE_PX;
12
+ constructor(board: Board);
13
+ start(): void;
14
+ stop(): void;
15
+ private getNodes;
16
+ private getConnectors;
17
+ private tick;
18
+ private syncPositions;
19
+ /** Re-wake the engine after user moves a node (dragging disturbs equilibrium). */
20
+ wake(): void;
21
+ }
@@ -247,6 +247,11 @@ export declare const conf: {
247
247
  };
248
248
  MAX_CARD_SIZE: number;
249
249
  CONNECTOR_ITEM_OFFSET: number;
250
+ FG_SPRING_K: number;
251
+ FG_TARGET_GAP: number;
252
+ FG_REPULSION: number;
253
+ FG_DAMPING: number;
254
+ FG_SLEEP_THRESHOLD: number;
250
255
  GRAVITY_G: number;
251
256
  GRAVITY_G_CENTER: number;
252
257
  GRAVITY_DAMPING: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "microboard-temp",
3
- "version": "0.13.15",
3
+ "version": "0.13.16",
4
4
  "description": "A flexible interactive whiteboard library",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",