@fusefactory/fuse-three-forcegraph 1.0.7 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -74,12 +74,10 @@ declare class CameraController {
74
74
  camera: THREE.PerspectiveCamera;
75
75
  controls: CameraControls;
76
76
  private sceneBounds;
77
- private domElement;
78
77
  private autorotateEnabled;
79
78
  private autorotateSpeed;
80
79
  private userIsActive;
81
80
  constructor(domelement: HTMLElement, config?: CameraConfig);
82
- private handleWheel;
83
81
  private setupDefaultControls;
84
82
  /**
85
83
  * Set camera control mode
@@ -1104,6 +1102,7 @@ declare class InputProcessor extends EventEmitter {
1104
1102
  private handlePointerDown;
1105
1103
  private handlePointerUp;
1106
1104
  private handlePointerLeave;
1105
+ private handlePointerCancel;
1107
1106
  private updateHover;
1108
1107
  /**
1109
1108
  * Update hover state even when pointer is stationary
@@ -1126,6 +1125,7 @@ declare class InteractionManager {
1126
1125
  private hoverHandler;
1127
1126
  private clickHandler;
1128
1127
  private dragHandler;
1128
+ private keyboardNavigator;
1129
1129
  tooltipManager: TooltipManager;
1130
1130
  private isDragging;
1131
1131
  isTooltipSticky: boolean;
@@ -1423,6 +1423,14 @@ declare class Engine {
1423
1423
  y: number;
1424
1424
  z: number;
1425
1425
  }, distance: number, transitionDuration?: number): Promise<void>;
1426
+ /**
1427
+ * Reset camera to its initial position and target
1428
+ */
1429
+ resetCamera(enableTransition?: boolean): Promise<void>;
1430
+ /**
1431
+ * Fit camera to show all currently visible nodes
1432
+ */
1433
+ fitToView(enableTransition?: boolean): Promise<void>;
1426
1434
  /**
1427
1435
  * Cleanup
1428
1436
  */
package/dist/index.mjs CHANGED
@@ -557,6 +557,7 @@ var InputProcessor = class extends EventEmitter {
557
557
  this.canvas.addEventListener("pointerdown", this.handlePointerDown);
558
558
  this.canvas.addEventListener("pointerup", this.handlePointerUp);
559
559
  this.canvas.addEventListener("pointerleave", this.handlePointerLeave);
560
+ this.canvas.addEventListener("pointercancel", this.handlePointerCancel);
560
561
  }
561
562
  handlePointerMove = (event) => {
562
563
  this.updatePointer(event);
@@ -590,6 +591,7 @@ var InputProcessor = class extends EventEmitter {
590
591
  }
591
592
  };
592
593
  handlePointerDown = (event) => {
594
+ this.canvas.setPointerCapture(event.pointerId);
593
595
  this.updatePointer(event);
594
596
  this.mouseDownTime = Date.now();
595
597
  this.isPointerDown = true;
@@ -607,6 +609,7 @@ var InputProcessor = class extends EventEmitter {
607
609
  });
608
610
  };
609
611
  handlePointerUp = (event) => {
612
+ this.updatePointer(event);
610
613
  const clickDuration = Date.now() - this.mouseDownTime;
611
614
  const wasClick = this.isTouch ? !this.isDragging : clickDuration < this.CLICK_THRESHOLD;
612
615
  if (this.isDragging) this.emit("pointer:dragend", {
@@ -637,6 +640,31 @@ var InputProcessor = class extends EventEmitter {
637
640
  this.lastClientX = null;
638
641
  this.lastClientY = null;
639
642
  this.updateHover(-1);
643
+ if (this.isDragging) {
644
+ this.emit("pointer:dragend", {
645
+ index: this.draggedIndex,
646
+ x: this.canvasPointer.x,
647
+ y: this.canvasPointer.y,
648
+ clientX: this.pointerDownX,
649
+ clientY: this.pointerDownY
650
+ });
651
+ this.isPointerDown = false;
652
+ this.isDragging = false;
653
+ this.draggedIndex = -1;
654
+ }
655
+ };
656
+ handlePointerCancel = (_event) => {
657
+ if (this.isDragging) this.emit("pointer:dragend", {
658
+ index: this.draggedIndex,
659
+ x: this.canvasPointer.x,
660
+ y: this.canvasPointer.y,
661
+ clientX: this.pointerDownX,
662
+ clientY: this.pointerDownY
663
+ });
664
+ this.isPointerDown = false;
665
+ this.isDragging = false;
666
+ this.draggedIndex = -1;
667
+ this.updateHover(-1);
640
668
  };
641
669
  updateHover(index) {
642
670
  const prevIndex = this.currentHoverIndex;
@@ -720,6 +748,72 @@ var InputProcessor = class extends EventEmitter {
720
748
  this.canvas.removeEventListener("pointerdown", this.handlePointerDown);
721
749
  this.canvas.removeEventListener("pointerup", this.handlePointerUp);
722
750
  this.canvas.removeEventListener("pointerleave", this.handlePointerLeave);
751
+ this.canvas.removeEventListener("pointercancel", this.handlePointerCancel);
752
+ }
753
+ };
754
+
755
+ //#endregion
756
+ //#region controls/KeyboardNavigator.ts
757
+ const PAN_STEP = .05;
758
+ const ORBIT_STEP = .05;
759
+ var KeyboardNavigator = class {
760
+ boundHandler;
761
+ constructor(camera, interactionManager, canvas) {
762
+ this.camera = camera;
763
+ this.interactionManager = interactionManager;
764
+ this.canvas = canvas;
765
+ this.boundHandler = this.handleKeyDown.bind(this);
766
+ canvas.setAttribute("tabindex", "0");
767
+ canvas.addEventListener("keydown", this.boundHandler);
768
+ }
769
+ handleKeyDown(e) {
770
+ if (e.metaKey || e.ctrlKey) return;
771
+ const c = this.camera.controls;
772
+ const radius = c._spherical.radius;
773
+ switch (e.key) {
774
+ case "ArrowLeft":
775
+ e.preventDefault();
776
+ e.shiftKey ? c.truck(-radius * PAN_STEP, 0, true) : c.rotate(-ORBIT_STEP, 0, true);
777
+ break;
778
+ case "ArrowRight":
779
+ e.preventDefault();
780
+ e.shiftKey ? c.truck(radius * PAN_STEP, 0, true) : c.rotate(ORBIT_STEP, 0, true);
781
+ break;
782
+ case "ArrowUp":
783
+ e.preventDefault();
784
+ e.shiftKey ? c.truck(0, -radius * PAN_STEP, true) : c.rotate(0, -ORBIT_STEP, true);
785
+ break;
786
+ case "ArrowDown":
787
+ e.preventDefault();
788
+ e.shiftKey ? c.truck(0, radius * PAN_STEP, true) : c.rotate(0, ORBIT_STEP, true);
789
+ break;
790
+ case "+":
791
+ case "=":
792
+ e.preventDefault();
793
+ c.dolly(-radius * .1, true);
794
+ break;
795
+ case "-":
796
+ case "_":
797
+ e.preventDefault();
798
+ c.dolly(radius * .1, true);
799
+ break;
800
+ case "f":
801
+ case "F":
802
+ case "Home":
803
+ e.preventDefault();
804
+ this.camera.reset(void 0, void 0, true);
805
+ break;
806
+ case "Escape":
807
+ if (this.interactionManager.isTooltipSticky) {
808
+ this.interactionManager.tooltipManager.hideFull();
809
+ this.interactionManager.isTooltipSticky = false;
810
+ this.interactionManager.stickyNodeId = null;
811
+ }
812
+ break;
813
+ }
814
+ }
815
+ dispose() {
816
+ this.canvas.removeEventListener("keydown", this.boundHandler);
723
817
  }
724
818
  };
725
819
 
@@ -730,6 +824,7 @@ var InteractionManager = class {
730
824
  hoverHandler;
731
825
  clickHandler;
732
826
  dragHandler;
827
+ keyboardNavigator = null;
733
828
  tooltipManager;
734
829
  isDragging = false;
735
830
  isTooltipSticky = false;
@@ -750,6 +845,7 @@ var InteractionManager = class {
750
845
  this.wireEvents();
751
846
  this.wireTooltipEvents();
752
847
  this.wireVisualFeedbackEvents();
848
+ if (cameraController) this.keyboardNavigator = new KeyboardNavigator(cameraController, this, canvas);
753
849
  }
754
850
  wireEvents() {
755
851
  this.pointerInput.on("pointer:hoverstart", (data) => {
@@ -909,6 +1005,7 @@ var InteractionManager = class {
909
1005
  this.hoverHandler.dispose();
910
1006
  this.clickHandler.dispose();
911
1007
  this.dragHandler.dispose();
1008
+ this.keyboardNavigator?.dispose();
912
1009
  this.tooltipManager.dispose();
913
1010
  }
914
1011
  };
@@ -927,7 +1024,6 @@ var CameraController = class {
927
1024
  camera;
928
1025
  controls;
929
1026
  sceneBounds = null;
930
- domElement;
931
1027
  autorotateEnabled = false;
932
1028
  autorotateSpeed = .5;
933
1029
  userIsActive = true;
@@ -938,22 +1034,9 @@ var CameraController = class {
938
1034
  else this.camera.position.set(0, 0, 2);
939
1035
  this.controls = new CameraControls(this.camera, domelement);
940
1036
  this.setupDefaultControls(config);
941
- this.handleWheel = this.handleWheel.bind(this);
942
- domelement.addEventListener("wheel", this.handleWheel, {
943
- capture: true,
944
- passive: true
945
- });
946
- }
947
- handleWheel() {
948
- if (!this.controls.dollyToCursor) return;
949
- const c = this.controls;
950
- if (c._spherical && c._sphericalEnd) {
951
- c._spherical.theta = c._sphericalEnd.theta;
952
- c._spherical.phi = c._sphericalEnd.phi;
953
- }
954
1037
  }
955
1038
  setupDefaultControls(config) {
956
- this.controls.smoothTime = .8;
1039
+ this.controls.smoothTime = .5;
957
1040
  this.controls.dollyToCursor = true;
958
1041
  this.controls.minDistance = config?.minDistance ?? 100;
959
1042
  this.controls.maxDistance = config?.maxDistance ?? 4e3;
@@ -1059,7 +1142,6 @@ var CameraController = class {
1059
1142
  * Dispose camera manager and clean up
1060
1143
  */
1061
1144
  dispose() {
1062
- this.domElement.removeEventListener("wheel", this.handleWheel, { capture: true });
1063
1145
  this.controls.dispose();
1064
1146
  }
1065
1147
  /**
@@ -4515,6 +4597,33 @@ var Engine = class {
4515
4597
  await this.setCameraLookAt(newPos, targetVec, transitionDuration);
4516
4598
  }
4517
4599
  /**
4600
+ * Reset camera to its initial position and target
4601
+ */
4602
+ async resetCamera(enableTransition = true) {
4603
+ await this.graphScene.camera.reset(void 0, void 0, enableTransition);
4604
+ }
4605
+ /**
4606
+ * Fit camera to show all currently visible nodes
4607
+ */
4608
+ async fitToView(enableTransition = true) {
4609
+ if (!this.simulationBuffers.isReady()) return;
4610
+ const positions = this.simulationBuffers.readPositions();
4611
+ const nodes = this.graphStore.getNodes();
4612
+ const box = new THREE.Box3();
4613
+ nodes.forEach((_node, i) => {
4614
+ if (positions[i * 4 + 3] === 0) return;
4615
+ box.expandByPoint(new THREE.Vector3(positions[i * 4], positions[i * 4 + 1], positions[i * 4 + 2]));
4616
+ });
4617
+ if (box.isEmpty()) return;
4618
+ const center = box.getCenter(new THREE.Vector3());
4619
+ const size = box.getSize(new THREE.Vector3());
4620
+ const maxDim = Math.max(size.x, size.y, size.z);
4621
+ const fov = this.graphScene.camera.camera.fov * THREE.MathUtils.DEG2RAD;
4622
+ const distance = maxDim / 2 / Math.tan(fov / 2) * 1.5;
4623
+ const camPos = center.clone().add(this.graphScene.camera.camera.position.clone().sub(center).normalize().multiplyScalar(distance));
4624
+ await this.graphScene.camera.setLookAt(camPos, center, enableTransition);
4625
+ }
4626
+ /**
4518
4627
  * Cleanup
4519
4628
  */
4520
4629
  dispose() {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fusefactory/fuse-three-forcegraph",
3
3
  "type": "module",
4
- "version": "1.0.7",
4
+ "version": "1.1.0",
5
5
  "packageManager": "pnpm@10.6.4",
6
6
  "description": "A high-performance GPU-accelerated force-directed graph visualization library built with Three.js. Features a modular pass-based architecture for flexible and extensible force simulations.",
7
7
  "author": "Matteo Amerena",
@@ -29,12 +29,12 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@rnbo/js": "^1.4.2",
32
- "camera-controls": "^3.1.1",
33
- "three": "^0.181.2"
32
+ "camera-controls": "https://github.com/ammlyy/camera-controls.git",
33
+ "three": "^0.183.2"
34
34
  },
35
35
  "devDependencies": {
36
- "@types/three": "^0.181.0",
37
- "tsdown": "0.20.0-beta.3",
36
+ "@types/three": "^0.183.1",
37
+ "tsdown": "^0.21.0",
38
38
  "typescript": "^5.9.3",
39
39
  "vite-plugin-glsl": "^1.5.5"
40
40
  },