microboard-temp 0.13.20 → 0.13.21

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.
@@ -54606,6 +54606,7 @@ class ForceGraphEngine {
54606
54606
  tickTimer = null;
54607
54607
  syncTimer = null;
54608
54608
  lastSyncedPositions = new Map;
54609
+ activeComponents = new Map;
54609
54610
  TICK_MS = 33;
54610
54611
  SYNC_MS = 300;
54611
54612
  SOFTENING_SQ = 100 * 100;
@@ -54613,18 +54614,60 @@ class ForceGraphEngine {
54613
54614
  constructor(board) {
54614
54615
  this.board = board;
54615
54616
  }
54616
- start() {
54617
- if (this.tickTimer !== null)
54617
+ enableForGraph(startNodeId) {
54618
+ if (this.isNodeInActiveGraph(startNodeId))
54618
54619
  return;
54619
- for (const item of this.getNodes()) {
54620
- const pos = item.transformation.getTranslation();
54621
- this.lastSyncedPositions.set(item.getId(), { x: pos.x, y: pos.y });
54622
- this.velocities.set(item.getId(), { vx: 0, vy: 0 });
54620
+ const nodeIds = this.bfsComponent(startNodeId);
54621
+ const targetGap = this.calibrateTargetGap(nodeIds);
54622
+ this.activeComponents.set(startNodeId, { nodeIds, targetGap });
54623
+ for (const id of nodeIds) {
54624
+ if (!this.velocities.has(id)) {
54625
+ this.velocities.set(id, { vx: 0, vy: 0 });
54626
+ }
54627
+ const item = this.board.items.getById(id);
54628
+ if (item && !this.lastSyncedPositions.has(id)) {
54629
+ const pos = item.transformation.getTranslation();
54630
+ this.lastSyncedPositions.set(id, { x: pos.x, y: pos.y });
54631
+ }
54632
+ }
54633
+ this.ensureRunning();
54634
+ }
54635
+ disableForGraph(nodeId) {
54636
+ const compId = this.findComponentId(nodeId);
54637
+ if (!compId)
54638
+ return;
54639
+ this.activeComponents.delete(compId);
54640
+ if (this.activeComponents.size === 0) {
54641
+ this.stopTimers();
54642
+ }
54643
+ }
54644
+ isNodeInActiveGraph(nodeId) {
54645
+ return !!this.findComponentId(nodeId);
54646
+ }
54647
+ hasActiveComponents() {
54648
+ return this.activeComponents.size > 0;
54649
+ }
54650
+ wake() {
54651
+ if (this.activeComponents.size > 0 && this.tickTimer === null) {
54652
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54623
54653
  }
54624
- this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54625
- this.syncTimer = setInterval(() => this.syncPositions(), this.SYNC_MS);
54626
54654
  }
54627
54655
  stop() {
54656
+ this.stopTimers();
54657
+ this.syncPositions();
54658
+ this.velocities.clear();
54659
+ this.lastSyncedPositions.clear();
54660
+ this.activeComponents.clear();
54661
+ }
54662
+ ensureRunning() {
54663
+ if (this.tickTimer === null) {
54664
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54665
+ }
54666
+ if (this.syncTimer === null) {
54667
+ this.syncTimer = setInterval(() => this.syncPositions(), this.SYNC_MS);
54668
+ }
54669
+ }
54670
+ stopTimers() {
54628
54671
  if (this.tickTimer !== null) {
54629
54672
  clearInterval(this.tickTimer);
54630
54673
  this.tickTimer = null;
@@ -54633,9 +54676,56 @@ class ForceGraphEngine {
54633
54676
  clearInterval(this.syncTimer);
54634
54677
  this.syncTimer = null;
54635
54678
  }
54636
- this.syncPositions();
54637
- this.velocities.clear();
54638
- this.lastSyncedPositions.clear();
54679
+ }
54680
+ findComponentId(nodeId) {
54681
+ for (const [compId, { nodeIds }] of this.activeComponents) {
54682
+ if (nodeIds.has(nodeId))
54683
+ return compId;
54684
+ }
54685
+ return;
54686
+ }
54687
+ bfsComponent(startNodeId) {
54688
+ const visited = new Set;
54689
+ const queue = [startNodeId];
54690
+ const connectors = this.getConnectors();
54691
+ while (queue.length > 0) {
54692
+ const nodeId = queue.shift();
54693
+ if (visited.has(nodeId))
54694
+ continue;
54695
+ visited.add(nodeId);
54696
+ for (const connector of connectors) {
54697
+ const { startItem, endItem } = connector.getConnectedItems();
54698
+ if (startItem?.getId() === nodeId && endItem && !visited.has(endItem.getId())) {
54699
+ queue.push(endItem.getId());
54700
+ }
54701
+ if (endItem?.getId() === nodeId && startItem && !visited.has(startItem.getId())) {
54702
+ queue.push(startItem.getId());
54703
+ }
54704
+ }
54705
+ }
54706
+ return visited;
54707
+ }
54708
+ calibrateTargetGap(nodeIds) {
54709
+ let totalMaxDim = 0;
54710
+ let count = 0;
54711
+ for (const id of nodeIds) {
54712
+ const item = this.board.items.getById(id);
54713
+ if (!item)
54714
+ continue;
54715
+ const mbr = item.getMbr();
54716
+ totalMaxDim += Math.max(mbr.getWidth(), mbr.getHeight());
54717
+ count++;
54718
+ }
54719
+ const avgMaxDim = count > 0 ? totalMaxDim / count : 100;
54720
+ return avgMaxDim * 0.3 + conf.FG_TARGET_GAP;
54721
+ }
54722
+ getActiveNodeIds() {
54723
+ const all6 = new Set;
54724
+ for (const { nodeIds } of this.activeComponents.values()) {
54725
+ for (const id of nodeIds)
54726
+ all6.add(id);
54727
+ }
54728
+ return all6;
54639
54729
  }
54640
54730
  getNodes() {
54641
54731
  return this.board.items.listAll().filter((item) => !EXCLUDED_TYPES.has(item.itemType) && !item.transformation.isLocked);
@@ -54645,8 +54735,10 @@ class ForceGraphEngine {
54645
54735
  }
54646
54736
  tick() {
54647
54737
  const dt = this.TICK_MS / 1000;
54648
- const nodes = this.getNodes();
54649
- if (nodes.length < 2)
54738
+ const activeIds = this.getActiveNodeIds();
54739
+ const allNodes = this.getNodes();
54740
+ const nodes = allNodes.filter((item) => activeIds.has(item.getId()));
54741
+ if (nodes.length < 1)
54650
54742
  return;
54651
54743
  const snapMap = new Map;
54652
54744
  for (const item of nodes) {
@@ -54687,7 +54779,9 @@ class ForceGraphEngine {
54687
54779
  const dx = s2.cx - s1.cx;
54688
54780
  const dy = s2.cy - s1.cy;
54689
54781
  const dist = Math.sqrt(dx * dx + dy * dy) + 0.001;
54690
- const targetDist = (Math.max(s1.w, s1.h) + Math.max(s2.w, s2.h)) * 0.5 + conf.FG_TARGET_GAP;
54782
+ const compId = this.findComponentId(s1.id);
54783
+ const targetGap = compId ? this.activeComponents.get(compId)?.targetGap ?? conf.FG_TARGET_GAP : conf.FG_TARGET_GAP;
54784
+ const targetDist = (Math.max(s1.w, s1.h) + Math.max(s2.w, s2.h)) * 0.5 + targetGap;
54691
54785
  const stretch = dist - targetDist;
54692
54786
  const forceMag = stretch * conf.FG_SPRING_K;
54693
54787
  const fx = dx / dist * forceMag;
@@ -54748,7 +54842,8 @@ class ForceGraphEngine {
54748
54842
  }
54749
54843
  }
54750
54844
  syncPositions() {
54751
- const nodes = this.getNodes();
54845
+ const activeIds = this.getActiveNodeIds();
54846
+ const nodes = this.getNodes().filter((item) => activeIds.has(item.getId()));
54752
54847
  if (nodes.length === 0)
54753
54848
  return;
54754
54849
  const movedItems = nodes.map((item) => {
@@ -54772,11 +54867,6 @@ class ForceGraphEngine {
54772
54867
  };
54773
54868
  this.board.events.emit(operation);
54774
54869
  }
54775
- wake() {
54776
- if (this.tickTimer === null && this.syncTimer !== null) {
54777
- this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54778
- }
54779
- }
54780
54870
  }
54781
54871
 
54782
54872
  // src/Board.ts
@@ -55852,20 +55942,22 @@ class Board {
55852
55942
  return this.gravity !== null;
55853
55943
  }
55854
55944
  forceGraph = null;
55855
- enableForceGraph() {
55856
- if (this.forceGraph)
55857
- return;
55858
- this.forceGraph = new ForceGraphEngine(this);
55859
- this.forceGraph.start();
55945
+ enableForceGraph(nodeId) {
55946
+ if (!this.forceGraph) {
55947
+ this.forceGraph = new ForceGraphEngine(this);
55948
+ }
55949
+ this.forceGraph.enableForGraph(nodeId);
55860
55950
  }
55861
- disableForceGraph() {
55951
+ disableForceGraph(nodeId) {
55862
55952
  if (!this.forceGraph)
55863
55953
  return;
55864
- this.forceGraph.stop();
55865
- this.forceGraph = null;
55954
+ this.forceGraph.disableForGraph(nodeId);
55955
+ if (!this.forceGraph.hasActiveComponents()) {
55956
+ this.forceGraph = null;
55957
+ }
55866
55958
  }
55867
- isForceGraphEnabled() {
55868
- return this.forceGraph !== null;
55959
+ isNodeInForceGraph(nodeId) {
55960
+ return this.forceGraph?.isNodeInActiveGraph(nodeId) ?? false;
55869
55961
  }
55870
55962
  wakeForceGraph() {
55871
55963
  this.forceGraph?.wake();
package/dist/cjs/index.js CHANGED
@@ -54606,6 +54606,7 @@ class ForceGraphEngine {
54606
54606
  tickTimer = null;
54607
54607
  syncTimer = null;
54608
54608
  lastSyncedPositions = new Map;
54609
+ activeComponents = new Map;
54609
54610
  TICK_MS = 33;
54610
54611
  SYNC_MS = 300;
54611
54612
  SOFTENING_SQ = 100 * 100;
@@ -54613,18 +54614,60 @@ class ForceGraphEngine {
54613
54614
  constructor(board) {
54614
54615
  this.board = board;
54615
54616
  }
54616
- start() {
54617
- if (this.tickTimer !== null)
54617
+ enableForGraph(startNodeId) {
54618
+ if (this.isNodeInActiveGraph(startNodeId))
54618
54619
  return;
54619
- for (const item of this.getNodes()) {
54620
- const pos = item.transformation.getTranslation();
54621
- this.lastSyncedPositions.set(item.getId(), { x: pos.x, y: pos.y });
54622
- this.velocities.set(item.getId(), { vx: 0, vy: 0 });
54620
+ const nodeIds = this.bfsComponent(startNodeId);
54621
+ const targetGap = this.calibrateTargetGap(nodeIds);
54622
+ this.activeComponents.set(startNodeId, { nodeIds, targetGap });
54623
+ for (const id of nodeIds) {
54624
+ if (!this.velocities.has(id)) {
54625
+ this.velocities.set(id, { vx: 0, vy: 0 });
54626
+ }
54627
+ const item = this.board.items.getById(id);
54628
+ if (item && !this.lastSyncedPositions.has(id)) {
54629
+ const pos = item.transformation.getTranslation();
54630
+ this.lastSyncedPositions.set(id, { x: pos.x, y: pos.y });
54631
+ }
54632
+ }
54633
+ this.ensureRunning();
54634
+ }
54635
+ disableForGraph(nodeId) {
54636
+ const compId = this.findComponentId(nodeId);
54637
+ if (!compId)
54638
+ return;
54639
+ this.activeComponents.delete(compId);
54640
+ if (this.activeComponents.size === 0) {
54641
+ this.stopTimers();
54642
+ }
54643
+ }
54644
+ isNodeInActiveGraph(nodeId) {
54645
+ return !!this.findComponentId(nodeId);
54646
+ }
54647
+ hasActiveComponents() {
54648
+ return this.activeComponents.size > 0;
54649
+ }
54650
+ wake() {
54651
+ if (this.activeComponents.size > 0 && this.tickTimer === null) {
54652
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54623
54653
  }
54624
- this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54625
- this.syncTimer = setInterval(() => this.syncPositions(), this.SYNC_MS);
54626
54654
  }
54627
54655
  stop() {
54656
+ this.stopTimers();
54657
+ this.syncPositions();
54658
+ this.velocities.clear();
54659
+ this.lastSyncedPositions.clear();
54660
+ this.activeComponents.clear();
54661
+ }
54662
+ ensureRunning() {
54663
+ if (this.tickTimer === null) {
54664
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54665
+ }
54666
+ if (this.syncTimer === null) {
54667
+ this.syncTimer = setInterval(() => this.syncPositions(), this.SYNC_MS);
54668
+ }
54669
+ }
54670
+ stopTimers() {
54628
54671
  if (this.tickTimer !== null) {
54629
54672
  clearInterval(this.tickTimer);
54630
54673
  this.tickTimer = null;
@@ -54633,9 +54676,56 @@ class ForceGraphEngine {
54633
54676
  clearInterval(this.syncTimer);
54634
54677
  this.syncTimer = null;
54635
54678
  }
54636
- this.syncPositions();
54637
- this.velocities.clear();
54638
- this.lastSyncedPositions.clear();
54679
+ }
54680
+ findComponentId(nodeId) {
54681
+ for (const [compId, { nodeIds }] of this.activeComponents) {
54682
+ if (nodeIds.has(nodeId))
54683
+ return compId;
54684
+ }
54685
+ return;
54686
+ }
54687
+ bfsComponent(startNodeId) {
54688
+ const visited = new Set;
54689
+ const queue = [startNodeId];
54690
+ const connectors = this.getConnectors();
54691
+ while (queue.length > 0) {
54692
+ const nodeId = queue.shift();
54693
+ if (visited.has(nodeId))
54694
+ continue;
54695
+ visited.add(nodeId);
54696
+ for (const connector of connectors) {
54697
+ const { startItem, endItem } = connector.getConnectedItems();
54698
+ if (startItem?.getId() === nodeId && endItem && !visited.has(endItem.getId())) {
54699
+ queue.push(endItem.getId());
54700
+ }
54701
+ if (endItem?.getId() === nodeId && startItem && !visited.has(startItem.getId())) {
54702
+ queue.push(startItem.getId());
54703
+ }
54704
+ }
54705
+ }
54706
+ return visited;
54707
+ }
54708
+ calibrateTargetGap(nodeIds) {
54709
+ let totalMaxDim = 0;
54710
+ let count = 0;
54711
+ for (const id of nodeIds) {
54712
+ const item = this.board.items.getById(id);
54713
+ if (!item)
54714
+ continue;
54715
+ const mbr = item.getMbr();
54716
+ totalMaxDim += Math.max(mbr.getWidth(), mbr.getHeight());
54717
+ count++;
54718
+ }
54719
+ const avgMaxDim = count > 0 ? totalMaxDim / count : 100;
54720
+ return avgMaxDim * 0.3 + conf.FG_TARGET_GAP;
54721
+ }
54722
+ getActiveNodeIds() {
54723
+ const all6 = new Set;
54724
+ for (const { nodeIds } of this.activeComponents.values()) {
54725
+ for (const id of nodeIds)
54726
+ all6.add(id);
54727
+ }
54728
+ return all6;
54639
54729
  }
54640
54730
  getNodes() {
54641
54731
  return this.board.items.listAll().filter((item) => !EXCLUDED_TYPES.has(item.itemType) && !item.transformation.isLocked);
@@ -54645,8 +54735,10 @@ class ForceGraphEngine {
54645
54735
  }
54646
54736
  tick() {
54647
54737
  const dt = this.TICK_MS / 1000;
54648
- const nodes = this.getNodes();
54649
- if (nodes.length < 2)
54738
+ const activeIds = this.getActiveNodeIds();
54739
+ const allNodes = this.getNodes();
54740
+ const nodes = allNodes.filter((item) => activeIds.has(item.getId()));
54741
+ if (nodes.length < 1)
54650
54742
  return;
54651
54743
  const snapMap = new Map;
54652
54744
  for (const item of nodes) {
@@ -54687,7 +54779,9 @@ class ForceGraphEngine {
54687
54779
  const dx = s2.cx - s1.cx;
54688
54780
  const dy = s2.cy - s1.cy;
54689
54781
  const dist = Math.sqrt(dx * dx + dy * dy) + 0.001;
54690
- const targetDist = (Math.max(s1.w, s1.h) + Math.max(s2.w, s2.h)) * 0.5 + conf.FG_TARGET_GAP;
54782
+ const compId = this.findComponentId(s1.id);
54783
+ const targetGap = compId ? this.activeComponents.get(compId)?.targetGap ?? conf.FG_TARGET_GAP : conf.FG_TARGET_GAP;
54784
+ const targetDist = (Math.max(s1.w, s1.h) + Math.max(s2.w, s2.h)) * 0.5 + targetGap;
54691
54785
  const stretch = dist - targetDist;
54692
54786
  const forceMag = stretch * conf.FG_SPRING_K;
54693
54787
  const fx = dx / dist * forceMag;
@@ -54748,7 +54842,8 @@ class ForceGraphEngine {
54748
54842
  }
54749
54843
  }
54750
54844
  syncPositions() {
54751
- const nodes = this.getNodes();
54845
+ const activeIds = this.getActiveNodeIds();
54846
+ const nodes = this.getNodes().filter((item) => activeIds.has(item.getId()));
54752
54847
  if (nodes.length === 0)
54753
54848
  return;
54754
54849
  const movedItems = nodes.map((item) => {
@@ -54772,11 +54867,6 @@ class ForceGraphEngine {
54772
54867
  };
54773
54868
  this.board.events.emit(operation);
54774
54869
  }
54775
- wake() {
54776
- if (this.tickTimer === null && this.syncTimer !== null) {
54777
- this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54778
- }
54779
- }
54780
54870
  }
54781
54871
 
54782
54872
  // src/Board.ts
@@ -55852,20 +55942,22 @@ class Board {
55852
55942
  return this.gravity !== null;
55853
55943
  }
55854
55944
  forceGraph = null;
55855
- enableForceGraph() {
55856
- if (this.forceGraph)
55857
- return;
55858
- this.forceGraph = new ForceGraphEngine(this);
55859
- this.forceGraph.start();
55945
+ enableForceGraph(nodeId) {
55946
+ if (!this.forceGraph) {
55947
+ this.forceGraph = new ForceGraphEngine(this);
55948
+ }
55949
+ this.forceGraph.enableForGraph(nodeId);
55860
55950
  }
55861
- disableForceGraph() {
55951
+ disableForceGraph(nodeId) {
55862
55952
  if (!this.forceGraph)
55863
55953
  return;
55864
- this.forceGraph.stop();
55865
- this.forceGraph = null;
55954
+ this.forceGraph.disableForGraph(nodeId);
55955
+ if (!this.forceGraph.hasActiveComponents()) {
55956
+ this.forceGraph = null;
55957
+ }
55866
55958
  }
55867
- isForceGraphEnabled() {
55868
- return this.forceGraph !== null;
55959
+ isNodeInForceGraph(nodeId) {
55960
+ return this.forceGraph?.isNodeInActiveGraph(nodeId) ?? false;
55869
55961
  }
55870
55962
  wakeForceGraph() {
55871
55963
  this.forceGraph?.wake();
package/dist/cjs/node.js CHANGED
@@ -57079,6 +57079,7 @@ class ForceGraphEngine {
57079
57079
  tickTimer = null;
57080
57080
  syncTimer = null;
57081
57081
  lastSyncedPositions = new Map;
57082
+ activeComponents = new Map;
57082
57083
  TICK_MS = 33;
57083
57084
  SYNC_MS = 300;
57084
57085
  SOFTENING_SQ = 100 * 100;
@@ -57086,18 +57087,60 @@ class ForceGraphEngine {
57086
57087
  constructor(board) {
57087
57088
  this.board = board;
57088
57089
  }
57089
- start() {
57090
- if (this.tickTimer !== null)
57090
+ enableForGraph(startNodeId) {
57091
+ if (this.isNodeInActiveGraph(startNodeId))
57091
57092
  return;
57092
- for (const item of this.getNodes()) {
57093
- const pos = item.transformation.getTranslation();
57094
- this.lastSyncedPositions.set(item.getId(), { x: pos.x, y: pos.y });
57095
- this.velocities.set(item.getId(), { vx: 0, vy: 0 });
57093
+ const nodeIds = this.bfsComponent(startNodeId);
57094
+ const targetGap = this.calibrateTargetGap(nodeIds);
57095
+ this.activeComponents.set(startNodeId, { nodeIds, targetGap });
57096
+ for (const id of nodeIds) {
57097
+ if (!this.velocities.has(id)) {
57098
+ this.velocities.set(id, { vx: 0, vy: 0 });
57099
+ }
57100
+ const item = this.board.items.getById(id);
57101
+ if (item && !this.lastSyncedPositions.has(id)) {
57102
+ const pos = item.transformation.getTranslation();
57103
+ this.lastSyncedPositions.set(id, { x: pos.x, y: pos.y });
57104
+ }
57105
+ }
57106
+ this.ensureRunning();
57107
+ }
57108
+ disableForGraph(nodeId) {
57109
+ const compId = this.findComponentId(nodeId);
57110
+ if (!compId)
57111
+ return;
57112
+ this.activeComponents.delete(compId);
57113
+ if (this.activeComponents.size === 0) {
57114
+ this.stopTimers();
57115
+ }
57116
+ }
57117
+ isNodeInActiveGraph(nodeId) {
57118
+ return !!this.findComponentId(nodeId);
57119
+ }
57120
+ hasActiveComponents() {
57121
+ return this.activeComponents.size > 0;
57122
+ }
57123
+ wake() {
57124
+ if (this.activeComponents.size > 0 && this.tickTimer === null) {
57125
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
57096
57126
  }
57097
- this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
57098
- this.syncTimer = setInterval(() => this.syncPositions(), this.SYNC_MS);
57099
57127
  }
57100
57128
  stop() {
57129
+ this.stopTimers();
57130
+ this.syncPositions();
57131
+ this.velocities.clear();
57132
+ this.lastSyncedPositions.clear();
57133
+ this.activeComponents.clear();
57134
+ }
57135
+ ensureRunning() {
57136
+ if (this.tickTimer === null) {
57137
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
57138
+ }
57139
+ if (this.syncTimer === null) {
57140
+ this.syncTimer = setInterval(() => this.syncPositions(), this.SYNC_MS);
57141
+ }
57142
+ }
57143
+ stopTimers() {
57101
57144
  if (this.tickTimer !== null) {
57102
57145
  clearInterval(this.tickTimer);
57103
57146
  this.tickTimer = null;
@@ -57106,9 +57149,56 @@ class ForceGraphEngine {
57106
57149
  clearInterval(this.syncTimer);
57107
57150
  this.syncTimer = null;
57108
57151
  }
57109
- this.syncPositions();
57110
- this.velocities.clear();
57111
- this.lastSyncedPositions.clear();
57152
+ }
57153
+ findComponentId(nodeId) {
57154
+ for (const [compId, { nodeIds }] of this.activeComponents) {
57155
+ if (nodeIds.has(nodeId))
57156
+ return compId;
57157
+ }
57158
+ return;
57159
+ }
57160
+ bfsComponent(startNodeId) {
57161
+ const visited = new Set;
57162
+ const queue = [startNodeId];
57163
+ const connectors = this.getConnectors();
57164
+ while (queue.length > 0) {
57165
+ const nodeId = queue.shift();
57166
+ if (visited.has(nodeId))
57167
+ continue;
57168
+ visited.add(nodeId);
57169
+ for (const connector of connectors) {
57170
+ const { startItem, endItem } = connector.getConnectedItems();
57171
+ if (startItem?.getId() === nodeId && endItem && !visited.has(endItem.getId())) {
57172
+ queue.push(endItem.getId());
57173
+ }
57174
+ if (endItem?.getId() === nodeId && startItem && !visited.has(startItem.getId())) {
57175
+ queue.push(startItem.getId());
57176
+ }
57177
+ }
57178
+ }
57179
+ return visited;
57180
+ }
57181
+ calibrateTargetGap(nodeIds) {
57182
+ let totalMaxDim = 0;
57183
+ let count = 0;
57184
+ for (const id of nodeIds) {
57185
+ const item = this.board.items.getById(id);
57186
+ if (!item)
57187
+ continue;
57188
+ const mbr = item.getMbr();
57189
+ totalMaxDim += Math.max(mbr.getWidth(), mbr.getHeight());
57190
+ count++;
57191
+ }
57192
+ const avgMaxDim = count > 0 ? totalMaxDim / count : 100;
57193
+ return avgMaxDim * 0.3 + conf.FG_TARGET_GAP;
57194
+ }
57195
+ getActiveNodeIds() {
57196
+ const all6 = new Set;
57197
+ for (const { nodeIds } of this.activeComponents.values()) {
57198
+ for (const id of nodeIds)
57199
+ all6.add(id);
57200
+ }
57201
+ return all6;
57112
57202
  }
57113
57203
  getNodes() {
57114
57204
  return this.board.items.listAll().filter((item) => !EXCLUDED_TYPES.has(item.itemType) && !item.transformation.isLocked);
@@ -57118,8 +57208,10 @@ class ForceGraphEngine {
57118
57208
  }
57119
57209
  tick() {
57120
57210
  const dt = this.TICK_MS / 1000;
57121
- const nodes = this.getNodes();
57122
- if (nodes.length < 2)
57211
+ const activeIds = this.getActiveNodeIds();
57212
+ const allNodes = this.getNodes();
57213
+ const nodes = allNodes.filter((item) => activeIds.has(item.getId()));
57214
+ if (nodes.length < 1)
57123
57215
  return;
57124
57216
  const snapMap = new Map;
57125
57217
  for (const item of nodes) {
@@ -57160,7 +57252,9 @@ class ForceGraphEngine {
57160
57252
  const dx = s2.cx - s1.cx;
57161
57253
  const dy = s2.cy - s1.cy;
57162
57254
  const dist = Math.sqrt(dx * dx + dy * dy) + 0.001;
57163
- const targetDist = (Math.max(s1.w, s1.h) + Math.max(s2.w, s2.h)) * 0.5 + conf.FG_TARGET_GAP;
57255
+ const compId = this.findComponentId(s1.id);
57256
+ const targetGap = compId ? this.activeComponents.get(compId)?.targetGap ?? conf.FG_TARGET_GAP : conf.FG_TARGET_GAP;
57257
+ const targetDist = (Math.max(s1.w, s1.h) + Math.max(s2.w, s2.h)) * 0.5 + targetGap;
57164
57258
  const stretch = dist - targetDist;
57165
57259
  const forceMag = stretch * conf.FG_SPRING_K;
57166
57260
  const fx = dx / dist * forceMag;
@@ -57221,7 +57315,8 @@ class ForceGraphEngine {
57221
57315
  }
57222
57316
  }
57223
57317
  syncPositions() {
57224
- const nodes = this.getNodes();
57318
+ const activeIds = this.getActiveNodeIds();
57319
+ const nodes = this.getNodes().filter((item) => activeIds.has(item.getId()));
57225
57320
  if (nodes.length === 0)
57226
57321
  return;
57227
57322
  const movedItems = nodes.map((item) => {
@@ -57245,11 +57340,6 @@ class ForceGraphEngine {
57245
57340
  };
57246
57341
  this.board.events.emit(operation);
57247
57342
  }
57248
- wake() {
57249
- if (this.tickTimer === null && this.syncTimer !== null) {
57250
- this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
57251
- }
57252
- }
57253
57343
  }
57254
57344
 
57255
57345
  // src/Board.ts
@@ -58325,20 +58415,22 @@ class Board {
58325
58415
  return this.gravity !== null;
58326
58416
  }
58327
58417
  forceGraph = null;
58328
- enableForceGraph() {
58329
- if (this.forceGraph)
58330
- return;
58331
- this.forceGraph = new ForceGraphEngine(this);
58332
- this.forceGraph.start();
58418
+ enableForceGraph(nodeId) {
58419
+ if (!this.forceGraph) {
58420
+ this.forceGraph = new ForceGraphEngine(this);
58421
+ }
58422
+ this.forceGraph.enableForGraph(nodeId);
58333
58423
  }
58334
- disableForceGraph() {
58424
+ disableForceGraph(nodeId) {
58335
58425
  if (!this.forceGraph)
58336
58426
  return;
58337
- this.forceGraph.stop();
58338
- this.forceGraph = null;
58427
+ this.forceGraph.disableForGraph(nodeId);
58428
+ if (!this.forceGraph.hasActiveComponents()) {
58429
+ this.forceGraph = null;
58430
+ }
58339
58431
  }
58340
- isForceGraphEnabled() {
58341
- return this.forceGraph !== null;
58432
+ isNodeInForceGraph(nodeId) {
58433
+ return this.forceGraph?.isNodeInActiveGraph(nodeId) ?? false;
58342
58434
  }
58343
58435
  wakeForceGraph() {
58344
58436
  this.forceGraph?.wake();
@@ -54435,6 +54435,7 @@ class ForceGraphEngine {
54435
54435
  tickTimer = null;
54436
54436
  syncTimer = null;
54437
54437
  lastSyncedPositions = new Map;
54438
+ activeComponents = new Map;
54438
54439
  TICK_MS = 33;
54439
54440
  SYNC_MS = 300;
54440
54441
  SOFTENING_SQ = 100 * 100;
@@ -54442,18 +54443,60 @@ class ForceGraphEngine {
54442
54443
  constructor(board) {
54443
54444
  this.board = board;
54444
54445
  }
54445
- start() {
54446
- if (this.tickTimer !== null)
54446
+ enableForGraph(startNodeId) {
54447
+ if (this.isNodeInActiveGraph(startNodeId))
54447
54448
  return;
54448
- for (const item of this.getNodes()) {
54449
- const pos = item.transformation.getTranslation();
54450
- this.lastSyncedPositions.set(item.getId(), { x: pos.x, y: pos.y });
54451
- this.velocities.set(item.getId(), { vx: 0, vy: 0 });
54449
+ const nodeIds = this.bfsComponent(startNodeId);
54450
+ const targetGap = this.calibrateTargetGap(nodeIds);
54451
+ this.activeComponents.set(startNodeId, { nodeIds, targetGap });
54452
+ for (const id of nodeIds) {
54453
+ if (!this.velocities.has(id)) {
54454
+ this.velocities.set(id, { vx: 0, vy: 0 });
54455
+ }
54456
+ const item = this.board.items.getById(id);
54457
+ if (item && !this.lastSyncedPositions.has(id)) {
54458
+ const pos = item.transformation.getTranslation();
54459
+ this.lastSyncedPositions.set(id, { x: pos.x, y: pos.y });
54460
+ }
54461
+ }
54462
+ this.ensureRunning();
54463
+ }
54464
+ disableForGraph(nodeId) {
54465
+ const compId = this.findComponentId(nodeId);
54466
+ if (!compId)
54467
+ return;
54468
+ this.activeComponents.delete(compId);
54469
+ if (this.activeComponents.size === 0) {
54470
+ this.stopTimers();
54471
+ }
54472
+ }
54473
+ isNodeInActiveGraph(nodeId) {
54474
+ return !!this.findComponentId(nodeId);
54475
+ }
54476
+ hasActiveComponents() {
54477
+ return this.activeComponents.size > 0;
54478
+ }
54479
+ wake() {
54480
+ if (this.activeComponents.size > 0 && this.tickTimer === null) {
54481
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54452
54482
  }
54453
- this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54454
- this.syncTimer = setInterval(() => this.syncPositions(), this.SYNC_MS);
54455
54483
  }
54456
54484
  stop() {
54485
+ this.stopTimers();
54486
+ this.syncPositions();
54487
+ this.velocities.clear();
54488
+ this.lastSyncedPositions.clear();
54489
+ this.activeComponents.clear();
54490
+ }
54491
+ ensureRunning() {
54492
+ if (this.tickTimer === null) {
54493
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54494
+ }
54495
+ if (this.syncTimer === null) {
54496
+ this.syncTimer = setInterval(() => this.syncPositions(), this.SYNC_MS);
54497
+ }
54498
+ }
54499
+ stopTimers() {
54457
54500
  if (this.tickTimer !== null) {
54458
54501
  clearInterval(this.tickTimer);
54459
54502
  this.tickTimer = null;
@@ -54462,9 +54505,56 @@ class ForceGraphEngine {
54462
54505
  clearInterval(this.syncTimer);
54463
54506
  this.syncTimer = null;
54464
54507
  }
54465
- this.syncPositions();
54466
- this.velocities.clear();
54467
- this.lastSyncedPositions.clear();
54508
+ }
54509
+ findComponentId(nodeId) {
54510
+ for (const [compId, { nodeIds }] of this.activeComponents) {
54511
+ if (nodeIds.has(nodeId))
54512
+ return compId;
54513
+ }
54514
+ return;
54515
+ }
54516
+ bfsComponent(startNodeId) {
54517
+ const visited = new Set;
54518
+ const queue = [startNodeId];
54519
+ const connectors = this.getConnectors();
54520
+ while (queue.length > 0) {
54521
+ const nodeId = queue.shift();
54522
+ if (visited.has(nodeId))
54523
+ continue;
54524
+ visited.add(nodeId);
54525
+ for (const connector of connectors) {
54526
+ const { startItem, endItem } = connector.getConnectedItems();
54527
+ if (startItem?.getId() === nodeId && endItem && !visited.has(endItem.getId())) {
54528
+ queue.push(endItem.getId());
54529
+ }
54530
+ if (endItem?.getId() === nodeId && startItem && !visited.has(startItem.getId())) {
54531
+ queue.push(startItem.getId());
54532
+ }
54533
+ }
54534
+ }
54535
+ return visited;
54536
+ }
54537
+ calibrateTargetGap(nodeIds) {
54538
+ let totalMaxDim = 0;
54539
+ let count = 0;
54540
+ for (const id of nodeIds) {
54541
+ const item = this.board.items.getById(id);
54542
+ if (!item)
54543
+ continue;
54544
+ const mbr = item.getMbr();
54545
+ totalMaxDim += Math.max(mbr.getWidth(), mbr.getHeight());
54546
+ count++;
54547
+ }
54548
+ const avgMaxDim = count > 0 ? totalMaxDim / count : 100;
54549
+ return avgMaxDim * 0.3 + conf.FG_TARGET_GAP;
54550
+ }
54551
+ getActiveNodeIds() {
54552
+ const all6 = new Set;
54553
+ for (const { nodeIds } of this.activeComponents.values()) {
54554
+ for (const id of nodeIds)
54555
+ all6.add(id);
54556
+ }
54557
+ return all6;
54468
54558
  }
54469
54559
  getNodes() {
54470
54560
  return this.board.items.listAll().filter((item) => !EXCLUDED_TYPES.has(item.itemType) && !item.transformation.isLocked);
@@ -54474,8 +54564,10 @@ class ForceGraphEngine {
54474
54564
  }
54475
54565
  tick() {
54476
54566
  const dt = this.TICK_MS / 1000;
54477
- const nodes = this.getNodes();
54478
- if (nodes.length < 2)
54567
+ const activeIds = this.getActiveNodeIds();
54568
+ const allNodes = this.getNodes();
54569
+ const nodes = allNodes.filter((item) => activeIds.has(item.getId()));
54570
+ if (nodes.length < 1)
54479
54571
  return;
54480
54572
  const snapMap = new Map;
54481
54573
  for (const item of nodes) {
@@ -54516,7 +54608,9 @@ class ForceGraphEngine {
54516
54608
  const dx = s2.cx - s1.cx;
54517
54609
  const dy = s2.cy - s1.cy;
54518
54610
  const dist = Math.sqrt(dx * dx + dy * dy) + 0.001;
54519
- const targetDist = (Math.max(s1.w, s1.h) + Math.max(s2.w, s2.h)) * 0.5 + conf.FG_TARGET_GAP;
54611
+ const compId = this.findComponentId(s1.id);
54612
+ const targetGap = compId ? this.activeComponents.get(compId)?.targetGap ?? conf.FG_TARGET_GAP : conf.FG_TARGET_GAP;
54613
+ const targetDist = (Math.max(s1.w, s1.h) + Math.max(s2.w, s2.h)) * 0.5 + targetGap;
54520
54614
  const stretch = dist - targetDist;
54521
54615
  const forceMag = stretch * conf.FG_SPRING_K;
54522
54616
  const fx = dx / dist * forceMag;
@@ -54577,7 +54671,8 @@ class ForceGraphEngine {
54577
54671
  }
54578
54672
  }
54579
54673
  syncPositions() {
54580
- const nodes = this.getNodes();
54674
+ const activeIds = this.getActiveNodeIds();
54675
+ const nodes = this.getNodes().filter((item) => activeIds.has(item.getId()));
54581
54676
  if (nodes.length === 0)
54582
54677
  return;
54583
54678
  const movedItems = nodes.map((item) => {
@@ -54601,11 +54696,6 @@ class ForceGraphEngine {
54601
54696
  };
54602
54697
  this.board.events.emit(operation);
54603
54698
  }
54604
- wake() {
54605
- if (this.tickTimer === null && this.syncTimer !== null) {
54606
- this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54607
- }
54608
- }
54609
54699
  }
54610
54700
 
54611
54701
  // src/Board.ts
@@ -55681,20 +55771,22 @@ class Board {
55681
55771
  return this.gravity !== null;
55682
55772
  }
55683
55773
  forceGraph = null;
55684
- enableForceGraph() {
55685
- if (this.forceGraph)
55686
- return;
55687
- this.forceGraph = new ForceGraphEngine(this);
55688
- this.forceGraph.start();
55774
+ enableForceGraph(nodeId) {
55775
+ if (!this.forceGraph) {
55776
+ this.forceGraph = new ForceGraphEngine(this);
55777
+ }
55778
+ this.forceGraph.enableForGraph(nodeId);
55689
55779
  }
55690
- disableForceGraph() {
55780
+ disableForceGraph(nodeId) {
55691
55781
  if (!this.forceGraph)
55692
55782
  return;
55693
- this.forceGraph.stop();
55694
- this.forceGraph = null;
55783
+ this.forceGraph.disableForGraph(nodeId);
55784
+ if (!this.forceGraph.hasActiveComponents()) {
55785
+ this.forceGraph = null;
55786
+ }
55695
55787
  }
55696
- isForceGraphEnabled() {
55697
- return this.forceGraph !== null;
55788
+ isNodeInForceGraph(nodeId) {
55789
+ return this.forceGraph?.isNodeInActiveGraph(nodeId) ?? false;
55698
55790
  }
55699
55791
  wakeForceGraph() {
55700
55792
  this.forceGraph?.wake();
package/dist/esm/index.js CHANGED
@@ -54428,6 +54428,7 @@ class ForceGraphEngine {
54428
54428
  tickTimer = null;
54429
54429
  syncTimer = null;
54430
54430
  lastSyncedPositions = new Map;
54431
+ activeComponents = new Map;
54431
54432
  TICK_MS = 33;
54432
54433
  SYNC_MS = 300;
54433
54434
  SOFTENING_SQ = 100 * 100;
@@ -54435,18 +54436,60 @@ class ForceGraphEngine {
54435
54436
  constructor(board) {
54436
54437
  this.board = board;
54437
54438
  }
54438
- start() {
54439
- if (this.tickTimer !== null)
54439
+ enableForGraph(startNodeId) {
54440
+ if (this.isNodeInActiveGraph(startNodeId))
54440
54441
  return;
54441
- for (const item of this.getNodes()) {
54442
- const pos = item.transformation.getTranslation();
54443
- this.lastSyncedPositions.set(item.getId(), { x: pos.x, y: pos.y });
54444
- this.velocities.set(item.getId(), { vx: 0, vy: 0 });
54442
+ const nodeIds = this.bfsComponent(startNodeId);
54443
+ const targetGap = this.calibrateTargetGap(nodeIds);
54444
+ this.activeComponents.set(startNodeId, { nodeIds, targetGap });
54445
+ for (const id of nodeIds) {
54446
+ if (!this.velocities.has(id)) {
54447
+ this.velocities.set(id, { vx: 0, vy: 0 });
54448
+ }
54449
+ const item = this.board.items.getById(id);
54450
+ if (item && !this.lastSyncedPositions.has(id)) {
54451
+ const pos = item.transformation.getTranslation();
54452
+ this.lastSyncedPositions.set(id, { x: pos.x, y: pos.y });
54453
+ }
54454
+ }
54455
+ this.ensureRunning();
54456
+ }
54457
+ disableForGraph(nodeId) {
54458
+ const compId = this.findComponentId(nodeId);
54459
+ if (!compId)
54460
+ return;
54461
+ this.activeComponents.delete(compId);
54462
+ if (this.activeComponents.size === 0) {
54463
+ this.stopTimers();
54464
+ }
54465
+ }
54466
+ isNodeInActiveGraph(nodeId) {
54467
+ return !!this.findComponentId(nodeId);
54468
+ }
54469
+ hasActiveComponents() {
54470
+ return this.activeComponents.size > 0;
54471
+ }
54472
+ wake() {
54473
+ if (this.activeComponents.size > 0 && this.tickTimer === null) {
54474
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54445
54475
  }
54446
- this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54447
- this.syncTimer = setInterval(() => this.syncPositions(), this.SYNC_MS);
54448
54476
  }
54449
54477
  stop() {
54478
+ this.stopTimers();
54479
+ this.syncPositions();
54480
+ this.velocities.clear();
54481
+ this.lastSyncedPositions.clear();
54482
+ this.activeComponents.clear();
54483
+ }
54484
+ ensureRunning() {
54485
+ if (this.tickTimer === null) {
54486
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54487
+ }
54488
+ if (this.syncTimer === null) {
54489
+ this.syncTimer = setInterval(() => this.syncPositions(), this.SYNC_MS);
54490
+ }
54491
+ }
54492
+ stopTimers() {
54450
54493
  if (this.tickTimer !== null) {
54451
54494
  clearInterval(this.tickTimer);
54452
54495
  this.tickTimer = null;
@@ -54455,9 +54498,56 @@ class ForceGraphEngine {
54455
54498
  clearInterval(this.syncTimer);
54456
54499
  this.syncTimer = null;
54457
54500
  }
54458
- this.syncPositions();
54459
- this.velocities.clear();
54460
- this.lastSyncedPositions.clear();
54501
+ }
54502
+ findComponentId(nodeId) {
54503
+ for (const [compId, { nodeIds }] of this.activeComponents) {
54504
+ if (nodeIds.has(nodeId))
54505
+ return compId;
54506
+ }
54507
+ return;
54508
+ }
54509
+ bfsComponent(startNodeId) {
54510
+ const visited = new Set;
54511
+ const queue = [startNodeId];
54512
+ const connectors = this.getConnectors();
54513
+ while (queue.length > 0) {
54514
+ const nodeId = queue.shift();
54515
+ if (visited.has(nodeId))
54516
+ continue;
54517
+ visited.add(nodeId);
54518
+ for (const connector of connectors) {
54519
+ const { startItem, endItem } = connector.getConnectedItems();
54520
+ if (startItem?.getId() === nodeId && endItem && !visited.has(endItem.getId())) {
54521
+ queue.push(endItem.getId());
54522
+ }
54523
+ if (endItem?.getId() === nodeId && startItem && !visited.has(startItem.getId())) {
54524
+ queue.push(startItem.getId());
54525
+ }
54526
+ }
54527
+ }
54528
+ return visited;
54529
+ }
54530
+ calibrateTargetGap(nodeIds) {
54531
+ let totalMaxDim = 0;
54532
+ let count = 0;
54533
+ for (const id of nodeIds) {
54534
+ const item = this.board.items.getById(id);
54535
+ if (!item)
54536
+ continue;
54537
+ const mbr = item.getMbr();
54538
+ totalMaxDim += Math.max(mbr.getWidth(), mbr.getHeight());
54539
+ count++;
54540
+ }
54541
+ const avgMaxDim = count > 0 ? totalMaxDim / count : 100;
54542
+ return avgMaxDim * 0.3 + conf.FG_TARGET_GAP;
54543
+ }
54544
+ getActiveNodeIds() {
54545
+ const all6 = new Set;
54546
+ for (const { nodeIds } of this.activeComponents.values()) {
54547
+ for (const id of nodeIds)
54548
+ all6.add(id);
54549
+ }
54550
+ return all6;
54461
54551
  }
54462
54552
  getNodes() {
54463
54553
  return this.board.items.listAll().filter((item) => !EXCLUDED_TYPES.has(item.itemType) && !item.transformation.isLocked);
@@ -54467,8 +54557,10 @@ class ForceGraphEngine {
54467
54557
  }
54468
54558
  tick() {
54469
54559
  const dt = this.TICK_MS / 1000;
54470
- const nodes = this.getNodes();
54471
- if (nodes.length < 2)
54560
+ const activeIds = this.getActiveNodeIds();
54561
+ const allNodes = this.getNodes();
54562
+ const nodes = allNodes.filter((item) => activeIds.has(item.getId()));
54563
+ if (nodes.length < 1)
54472
54564
  return;
54473
54565
  const snapMap = new Map;
54474
54566
  for (const item of nodes) {
@@ -54509,7 +54601,9 @@ class ForceGraphEngine {
54509
54601
  const dx = s2.cx - s1.cx;
54510
54602
  const dy = s2.cy - s1.cy;
54511
54603
  const dist = Math.sqrt(dx * dx + dy * dy) + 0.001;
54512
- const targetDist = (Math.max(s1.w, s1.h) + Math.max(s2.w, s2.h)) * 0.5 + conf.FG_TARGET_GAP;
54604
+ const compId = this.findComponentId(s1.id);
54605
+ const targetGap = compId ? this.activeComponents.get(compId)?.targetGap ?? conf.FG_TARGET_GAP : conf.FG_TARGET_GAP;
54606
+ const targetDist = (Math.max(s1.w, s1.h) + Math.max(s2.w, s2.h)) * 0.5 + targetGap;
54513
54607
  const stretch = dist - targetDist;
54514
54608
  const forceMag = stretch * conf.FG_SPRING_K;
54515
54609
  const fx = dx / dist * forceMag;
@@ -54570,7 +54664,8 @@ class ForceGraphEngine {
54570
54664
  }
54571
54665
  }
54572
54666
  syncPositions() {
54573
- const nodes = this.getNodes();
54667
+ const activeIds = this.getActiveNodeIds();
54668
+ const nodes = this.getNodes().filter((item) => activeIds.has(item.getId()));
54574
54669
  if (nodes.length === 0)
54575
54670
  return;
54576
54671
  const movedItems = nodes.map((item) => {
@@ -54594,11 +54689,6 @@ class ForceGraphEngine {
54594
54689
  };
54595
54690
  this.board.events.emit(operation);
54596
54691
  }
54597
- wake() {
54598
- if (this.tickTimer === null && this.syncTimer !== null) {
54599
- this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
54600
- }
54601
- }
54602
54692
  }
54603
54693
 
54604
54694
  // src/Board.ts
@@ -55674,20 +55764,22 @@ class Board {
55674
55764
  return this.gravity !== null;
55675
55765
  }
55676
55766
  forceGraph = null;
55677
- enableForceGraph() {
55678
- if (this.forceGraph)
55679
- return;
55680
- this.forceGraph = new ForceGraphEngine(this);
55681
- this.forceGraph.start();
55767
+ enableForceGraph(nodeId) {
55768
+ if (!this.forceGraph) {
55769
+ this.forceGraph = new ForceGraphEngine(this);
55770
+ }
55771
+ this.forceGraph.enableForGraph(nodeId);
55682
55772
  }
55683
- disableForceGraph() {
55773
+ disableForceGraph(nodeId) {
55684
55774
  if (!this.forceGraph)
55685
55775
  return;
55686
- this.forceGraph.stop();
55687
- this.forceGraph = null;
55776
+ this.forceGraph.disableForGraph(nodeId);
55777
+ if (!this.forceGraph.hasActiveComponents()) {
55778
+ this.forceGraph = null;
55779
+ }
55688
55780
  }
55689
- isForceGraphEnabled() {
55690
- return this.forceGraph !== null;
55781
+ isNodeInForceGraph(nodeId) {
55782
+ return this.forceGraph?.isNodeInActiveGraph(nodeId) ?? false;
55691
55783
  }
55692
55784
  wakeForceGraph() {
55693
55785
  this.forceGraph?.wake();
package/dist/esm/node.js CHANGED
@@ -56896,6 +56896,7 @@ class ForceGraphEngine {
56896
56896
  tickTimer = null;
56897
56897
  syncTimer = null;
56898
56898
  lastSyncedPositions = new Map;
56899
+ activeComponents = new Map;
56899
56900
  TICK_MS = 33;
56900
56901
  SYNC_MS = 300;
56901
56902
  SOFTENING_SQ = 100 * 100;
@@ -56903,18 +56904,60 @@ class ForceGraphEngine {
56903
56904
  constructor(board) {
56904
56905
  this.board = board;
56905
56906
  }
56906
- start() {
56907
- if (this.tickTimer !== null)
56907
+ enableForGraph(startNodeId) {
56908
+ if (this.isNodeInActiveGraph(startNodeId))
56908
56909
  return;
56909
- for (const item of this.getNodes()) {
56910
- const pos = item.transformation.getTranslation();
56911
- this.lastSyncedPositions.set(item.getId(), { x: pos.x, y: pos.y });
56912
- this.velocities.set(item.getId(), { vx: 0, vy: 0 });
56910
+ const nodeIds = this.bfsComponent(startNodeId);
56911
+ const targetGap = this.calibrateTargetGap(nodeIds);
56912
+ this.activeComponents.set(startNodeId, { nodeIds, targetGap });
56913
+ for (const id of nodeIds) {
56914
+ if (!this.velocities.has(id)) {
56915
+ this.velocities.set(id, { vx: 0, vy: 0 });
56916
+ }
56917
+ const item = this.board.items.getById(id);
56918
+ if (item && !this.lastSyncedPositions.has(id)) {
56919
+ const pos = item.transformation.getTranslation();
56920
+ this.lastSyncedPositions.set(id, { x: pos.x, y: pos.y });
56921
+ }
56922
+ }
56923
+ this.ensureRunning();
56924
+ }
56925
+ disableForGraph(nodeId) {
56926
+ const compId = this.findComponentId(nodeId);
56927
+ if (!compId)
56928
+ return;
56929
+ this.activeComponents.delete(compId);
56930
+ if (this.activeComponents.size === 0) {
56931
+ this.stopTimers();
56932
+ }
56933
+ }
56934
+ isNodeInActiveGraph(nodeId) {
56935
+ return !!this.findComponentId(nodeId);
56936
+ }
56937
+ hasActiveComponents() {
56938
+ return this.activeComponents.size > 0;
56939
+ }
56940
+ wake() {
56941
+ if (this.activeComponents.size > 0 && this.tickTimer === null) {
56942
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
56913
56943
  }
56914
- this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
56915
- this.syncTimer = setInterval(() => this.syncPositions(), this.SYNC_MS);
56916
56944
  }
56917
56945
  stop() {
56946
+ this.stopTimers();
56947
+ this.syncPositions();
56948
+ this.velocities.clear();
56949
+ this.lastSyncedPositions.clear();
56950
+ this.activeComponents.clear();
56951
+ }
56952
+ ensureRunning() {
56953
+ if (this.tickTimer === null) {
56954
+ this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
56955
+ }
56956
+ if (this.syncTimer === null) {
56957
+ this.syncTimer = setInterval(() => this.syncPositions(), this.SYNC_MS);
56958
+ }
56959
+ }
56960
+ stopTimers() {
56918
56961
  if (this.tickTimer !== null) {
56919
56962
  clearInterval(this.tickTimer);
56920
56963
  this.tickTimer = null;
@@ -56923,9 +56966,56 @@ class ForceGraphEngine {
56923
56966
  clearInterval(this.syncTimer);
56924
56967
  this.syncTimer = null;
56925
56968
  }
56926
- this.syncPositions();
56927
- this.velocities.clear();
56928
- this.lastSyncedPositions.clear();
56969
+ }
56970
+ findComponentId(nodeId) {
56971
+ for (const [compId, { nodeIds }] of this.activeComponents) {
56972
+ if (nodeIds.has(nodeId))
56973
+ return compId;
56974
+ }
56975
+ return;
56976
+ }
56977
+ bfsComponent(startNodeId) {
56978
+ const visited = new Set;
56979
+ const queue = [startNodeId];
56980
+ const connectors = this.getConnectors();
56981
+ while (queue.length > 0) {
56982
+ const nodeId = queue.shift();
56983
+ if (visited.has(nodeId))
56984
+ continue;
56985
+ visited.add(nodeId);
56986
+ for (const connector of connectors) {
56987
+ const { startItem, endItem } = connector.getConnectedItems();
56988
+ if (startItem?.getId() === nodeId && endItem && !visited.has(endItem.getId())) {
56989
+ queue.push(endItem.getId());
56990
+ }
56991
+ if (endItem?.getId() === nodeId && startItem && !visited.has(startItem.getId())) {
56992
+ queue.push(startItem.getId());
56993
+ }
56994
+ }
56995
+ }
56996
+ return visited;
56997
+ }
56998
+ calibrateTargetGap(nodeIds) {
56999
+ let totalMaxDim = 0;
57000
+ let count = 0;
57001
+ for (const id of nodeIds) {
57002
+ const item = this.board.items.getById(id);
57003
+ if (!item)
57004
+ continue;
57005
+ const mbr = item.getMbr();
57006
+ totalMaxDim += Math.max(mbr.getWidth(), mbr.getHeight());
57007
+ count++;
57008
+ }
57009
+ const avgMaxDim = count > 0 ? totalMaxDim / count : 100;
57010
+ return avgMaxDim * 0.3 + conf.FG_TARGET_GAP;
57011
+ }
57012
+ getActiveNodeIds() {
57013
+ const all6 = new Set;
57014
+ for (const { nodeIds } of this.activeComponents.values()) {
57015
+ for (const id of nodeIds)
57016
+ all6.add(id);
57017
+ }
57018
+ return all6;
56929
57019
  }
56930
57020
  getNodes() {
56931
57021
  return this.board.items.listAll().filter((item) => !EXCLUDED_TYPES.has(item.itemType) && !item.transformation.isLocked);
@@ -56935,8 +57025,10 @@ class ForceGraphEngine {
56935
57025
  }
56936
57026
  tick() {
56937
57027
  const dt = this.TICK_MS / 1000;
56938
- const nodes = this.getNodes();
56939
- if (nodes.length < 2)
57028
+ const activeIds = this.getActiveNodeIds();
57029
+ const allNodes = this.getNodes();
57030
+ const nodes = allNodes.filter((item) => activeIds.has(item.getId()));
57031
+ if (nodes.length < 1)
56940
57032
  return;
56941
57033
  const snapMap = new Map;
56942
57034
  for (const item of nodes) {
@@ -56977,7 +57069,9 @@ class ForceGraphEngine {
56977
57069
  const dx = s2.cx - s1.cx;
56978
57070
  const dy = s2.cy - s1.cy;
56979
57071
  const dist = Math.sqrt(dx * dx + dy * dy) + 0.001;
56980
- const targetDist = (Math.max(s1.w, s1.h) + Math.max(s2.w, s2.h)) * 0.5 + conf.FG_TARGET_GAP;
57072
+ const compId = this.findComponentId(s1.id);
57073
+ const targetGap = compId ? this.activeComponents.get(compId)?.targetGap ?? conf.FG_TARGET_GAP : conf.FG_TARGET_GAP;
57074
+ const targetDist = (Math.max(s1.w, s1.h) + Math.max(s2.w, s2.h)) * 0.5 + targetGap;
56981
57075
  const stretch = dist - targetDist;
56982
57076
  const forceMag = stretch * conf.FG_SPRING_K;
56983
57077
  const fx = dx / dist * forceMag;
@@ -57038,7 +57132,8 @@ class ForceGraphEngine {
57038
57132
  }
57039
57133
  }
57040
57134
  syncPositions() {
57041
- const nodes = this.getNodes();
57135
+ const activeIds = this.getActiveNodeIds();
57136
+ const nodes = this.getNodes().filter((item) => activeIds.has(item.getId()));
57042
57137
  if (nodes.length === 0)
57043
57138
  return;
57044
57139
  const movedItems = nodes.map((item) => {
@@ -57062,11 +57157,6 @@ class ForceGraphEngine {
57062
57157
  };
57063
57158
  this.board.events.emit(operation);
57064
57159
  }
57065
- wake() {
57066
- if (this.tickTimer === null && this.syncTimer !== null) {
57067
- this.tickTimer = setInterval(() => this.tick(), this.TICK_MS);
57068
- }
57069
- }
57070
57160
  }
57071
57161
 
57072
57162
  // src/Board.ts
@@ -58142,20 +58232,22 @@ class Board {
58142
58232
  return this.gravity !== null;
58143
58233
  }
58144
58234
  forceGraph = null;
58145
- enableForceGraph() {
58146
- if (this.forceGraph)
58147
- return;
58148
- this.forceGraph = new ForceGraphEngine(this);
58149
- this.forceGraph.start();
58235
+ enableForceGraph(nodeId) {
58236
+ if (!this.forceGraph) {
58237
+ this.forceGraph = new ForceGraphEngine(this);
58238
+ }
58239
+ this.forceGraph.enableForGraph(nodeId);
58150
58240
  }
58151
- disableForceGraph() {
58241
+ disableForceGraph(nodeId) {
58152
58242
  if (!this.forceGraph)
58153
58243
  return;
58154
- this.forceGraph.stop();
58155
- this.forceGraph = null;
58244
+ this.forceGraph.disableForGraph(nodeId);
58245
+ if (!this.forceGraph.hasActiveComponents()) {
58246
+ this.forceGraph = null;
58247
+ }
58156
58248
  }
58157
- isForceGraphEnabled() {
58158
- return this.forceGraph !== null;
58249
+ isNodeInForceGraph(nodeId) {
58250
+ return this.forceGraph?.isNodeInActiveGraph(nodeId) ?? false;
58159
58251
  }
58160
58252
  wakeForceGraph() {
58161
58253
  this.forceGraph?.wake();
@@ -140,9 +140,12 @@ export declare class Board {
140
140
  disableGravity(): void;
141
141
  isGravityEnabled(): boolean;
142
142
  private forceGraph;
143
- enableForceGraph(): void;
144
- disableForceGraph(): void;
145
- isForceGraphEnabled(): boolean;
143
+ /** Enable force-directed layout for the connected component containing `nodeId`. */
144
+ enableForceGraph(nodeId: string): void;
145
+ /** Disable graph mode for the component containing `nodeId`. */
146
+ disableForceGraph(nodeId: string): void;
147
+ /** Returns true if `nodeId` is currently in an active force-directed component. */
148
+ isNodeInForceGraph(nodeId: string): boolean;
146
149
  /** Call after dragging a node to re-wake the physics engine if it was sleeping. */
147
150
  wakeForceGraph(): void;
148
151
  }
@@ -5,17 +5,45 @@ export declare class ForceGraphEngine {
5
5
  private tickTimer;
6
6
  private syncTimer;
7
7
  private lastSyncedPositions;
8
+ /** Active components: componentId → { nodeIds, targetGap }
9
+ * componentId is the nodeId that was passed to enableForGraph(). */
10
+ private activeComponents;
8
11
  private readonly TICK_MS;
9
12
  private readonly SYNC_MS;
10
13
  private readonly SOFTENING_SQ;
11
14
  private readonly MIN_MOVE_PX;
12
15
  constructor(board: Board);
13
- start(): void;
16
+ /**
17
+ * Enable force-directed layout for the connected component containing `startNodeId`.
18
+ * BFS walks the connector graph to find all nodes in the component.
19
+ * `targetGap` is auto-calibrated from the average node size; callers may override via conf.
20
+ */
21
+ enableForGraph(startNodeId: string): void;
22
+ /**
23
+ * Disable graph mode for the component containing `nodeId`.
24
+ * Stops the engine entirely if no components remain active.
25
+ */
26
+ disableForGraph(nodeId: string): void;
27
+ isNodeInActiveGraph(nodeId: string): boolean;
28
+ hasActiveComponents(): boolean;
29
+ /** Re-wake physics after a node is manually dragged. */
30
+ wake(): void;
31
+ /** Full stop — called when Board destroys the engine. */
14
32
  stop(): void;
33
+ private ensureRunning;
34
+ private stopTimers;
35
+ /** Find the componentId (Map key) for the component containing `nodeId`. */
36
+ private findComponentId;
37
+ /** BFS through connector graph starting from `startNodeId`. */
38
+ private bfsComponent;
39
+ /**
40
+ * Auto-calibrate spring target gap from the average max(w, h) of nodes in the component.
41
+ * Larger nodes → longer springs so items visually breathe.
42
+ */
43
+ private calibrateTargetGap;
44
+ private getActiveNodeIds;
15
45
  private getNodes;
16
46
  private getConnectors;
17
47
  private tick;
18
48
  private syncPositions;
19
- /** Re-wake the engine after user moves a node (dragging disturbs equilibrium). */
20
- wake(): void;
21
49
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "microboard-temp",
3
- "version": "0.13.20",
3
+ "version": "0.13.21",
4
4
  "description": "A flexible interactive whiteboard library",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",