@yggy-tech/graphraum 0.36.0 → 0.37.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.ts CHANGED
@@ -80,6 +80,16 @@ interface GraphraumSetDataOptions {
80
80
  */
81
81
  fit?: boolean;
82
82
  }
83
+ type GraphraumNodeDragPhase = "start" | "move" | "end" | "cancel";
84
+ /** One step of a node drag. `cancel` carries the position the node was restored to (or left at). */
85
+ interface GraphraumNodeDragEvent {
86
+ nodeId: string;
87
+ phase: GraphraumNodeDragPhase;
88
+ position: GraphraumPosition;
89
+ }
90
+ interface GraphraumNodeDragOptions {
91
+ enabled: boolean;
92
+ }
83
93
  interface GraphraumCameraPoint3d {
84
94
  x: number;
85
95
  y: number;
@@ -618,6 +628,8 @@ declare class Graphraum<NodeAttributes = undefined, EdgeAttributes = undefined>
618
628
  private gpuTimer;
619
629
  private readonly viewListeners;
620
630
  private readonly focusListeners;
631
+ private readonly nodeDragListeners;
632
+ private readonly nodeDrag;
621
633
  private lastHoveredNodeId;
622
634
  private readonly antialias;
623
635
  private depthOfField;
@@ -715,6 +727,16 @@ declare class Graphraum<NodeAttributes = undefined, EdgeAttributes = undefined>
715
727
  createOverlay(options?: GraphraumOverlayOptions): GraphraumOverlay<NodeAttributes, EdgeAttributes>;
716
728
  /** Subscribes to camera and viewport changes. Returns an unsubscribe function. */
717
729
  onViewChange(listener: () => void): () => boolean;
730
+ /**
731
+ * Turns node dragging on or off (default off). A drag starts when the pointer moves past a small
732
+ * tolerance after a press on a node (a finger must rest on the node first, so a swipe still pans
733
+ * the camera), moves the node on a plane facing the camera, and ends on release. Escape or a
734
+ * cancelled pointer put the node back. Not available while the density LOD shows clusters.
735
+ */
736
+ setNodeDrag(options: GraphraumNodeDragOptions): void;
737
+ /** Subscribes to node drag steps (`start`, `move`, `end`, `cancel`). Returns an unsubscribe function. */
738
+ onNodeDrag(listener: (event: GraphraumNodeDragEvent) => void): () => boolean;
739
+ private createNodeDragHost;
718
740
  /** Subscribes to selection and hover changes that affect overlay focus chrome. */
719
741
  onFocusChange(listener: () => void): () => boolean;
720
742
  private notifyFocusChange;
@@ -983,4 +1005,4 @@ declare function isTransparentGraphraumBackground(background: GraphraumBackgroun
983
1005
  /** Defines typed, serializable graph visuals without coupling application data to the render loop. */
984
1006
  declare function defineVisuals<NodeAttributes = undefined, EdgeAttributes = undefined>(visuals: GraphraumVisualMapper<NodeAttributes, EdgeAttributes>): GraphraumVisualMapper<NodeAttributes, EdgeAttributes>;
985
1007
  //#endregion
986
- export { type CompiledGraphraumPresentation, DEFAULT_ENDPOINT_ATTACH, DEFAULT_ENDPOINT_CLEARANCE, DEFAULT_FORCE_SETTINGS, type EndpointAttach, type EndpointOutline, type ForceLayoutRequest, type ForceSettings, type GraphologyEdgeAttributes, type GraphologyNodeAttributes, type GraphologyRenderer, Graphraum, type GraphraumAutoOrbitOptions, type GraphraumAutoOrbitStatus, type GraphraumBackground, type GraphraumCameraPoint3d, type GraphraumCameraState, type GraphraumCameraState2d, type GraphraumCameraState3d, type GraphraumColor, type GraphraumData, type GraphraumDataPatch, type GraphraumDepthOfFieldOptions, type GraphraumDepthOfFieldStatus, type GraphraumDepthOfFieldTransition, type GraphraumDepthOfFieldUpdate, type GraphraumDiagnostics, type GraphraumEasing, type GraphraumEdge, type GraphraumEdgeEncoding, type GraphraumEdgeMarker, type GraphraumEdgeMarkerEnd, type GraphraumEdgePath, type GraphraumEdgeState, type GraphraumEdgeStyle, type GraphraumEdgeVisual, type GraphraumFlyToOptions, type GraphraumLabelCandidate, type GraphraumLayoutPositions, type GraphraumMode, type GraphraumNode, type GraphraumNodeEncoding, type GraphraumNodeGeometry, type GraphraumNodeShape, type GraphraumNodeState, type GraphraumNodeUpdate, type GraphraumNodeVisual, type GraphraumOptions, GraphraumOverlay, type GraphraumOverlayNode, type GraphraumOverlayOptions, type GraphraumPatternBackground, type GraphraumPickHit, type GraphraumPosition, type GraphraumPresentation, type GraphraumPresentationAction, type GraphraumPresentationProperty, type GraphraumPropertyValue, type GraphraumScreenPosition, type GraphraumSetDataOptions, type GraphraumTheme, type GraphraumThemeName, type GraphraumVisualMapper, type LabelBudgetCandidate, type RichNodePolicy, type SelectBudgetedLabelIdsInput, type SelectFocusLabelIdsInput, attachPointOnOutline, bindGraphology, boundOverlayIds, computeClusteredForcePositions, computeForcePositions, createForceSimulation, defineVisuals, forceIterationCount, graphraumEdgePaths, graphraumNodeShapes, graphraumTheme, graphraumThemeDark, graphraumThemeLight, graphraumThemes, isTransparentGraphraumBackground, normalizeGraphraumBackground, orderFocusNodeIds, resolveGraphraumTheme, selectBudgetedLabelIds, selectFocusLabelIds, selectRichNodeIds, trimEdgeEndpoints };
1008
+ export { type CompiledGraphraumPresentation, DEFAULT_ENDPOINT_ATTACH, DEFAULT_ENDPOINT_CLEARANCE, DEFAULT_FORCE_SETTINGS, type EndpointAttach, type EndpointOutline, type ForceLayoutRequest, type ForceSettings, type GraphologyEdgeAttributes, type GraphologyNodeAttributes, type GraphologyRenderer, Graphraum, type GraphraumAutoOrbitOptions, type GraphraumAutoOrbitStatus, type GraphraumBackground, type GraphraumCameraPoint3d, type GraphraumCameraState, type GraphraumCameraState2d, type GraphraumCameraState3d, type GraphraumColor, type GraphraumData, type GraphraumDataPatch, type GraphraumDepthOfFieldOptions, type GraphraumDepthOfFieldStatus, type GraphraumDepthOfFieldTransition, type GraphraumDepthOfFieldUpdate, type GraphraumDiagnostics, type GraphraumEasing, type GraphraumEdge, type GraphraumEdgeEncoding, type GraphraumEdgeMarker, type GraphraumEdgeMarkerEnd, type GraphraumEdgePath, type GraphraumEdgeState, type GraphraumEdgeStyle, type GraphraumEdgeVisual, type GraphraumFlyToOptions, type GraphraumLabelCandidate, type GraphraumLayoutPositions, type GraphraumMode, type GraphraumNode, type GraphraumNodeDragEvent, type GraphraumNodeDragOptions, type GraphraumNodeDragPhase, type GraphraumNodeEncoding, type GraphraumNodeGeometry, type GraphraumNodeShape, type GraphraumNodeState, type GraphraumNodeUpdate, type GraphraumNodeVisual, type GraphraumOptions, GraphraumOverlay, type GraphraumOverlayNode, type GraphraumOverlayOptions, type GraphraumPatternBackground, type GraphraumPickHit, type GraphraumPosition, type GraphraumPresentation, type GraphraumPresentationAction, type GraphraumPresentationProperty, type GraphraumPropertyValue, type GraphraumScreenPosition, type GraphraumSetDataOptions, type GraphraumTheme, type GraphraumThemeName, type GraphraumVisualMapper, type LabelBudgetCandidate, type RichNodePolicy, type SelectBudgetedLabelIdsInput, type SelectFocusLabelIdsInput, attachPointOnOutline, bindGraphology, boundOverlayIds, computeClusteredForcePositions, computeForcePositions, createForceSimulation, defineVisuals, forceIterationCount, graphraumEdgePaths, graphraumNodeShapes, graphraumTheme, graphraumThemeDark, graphraumThemeLight, graphraumThemes, isTransparentGraphraumBackground, normalizeGraphraumBackground, orderFocusNodeIds, resolveGraphraumTheme, selectBudgetedLabelIds, selectFocusLabelIds, selectRichNodeIds, trimEdgeEndpoints };
package/dist/index.js CHANGED
@@ -2493,6 +2493,231 @@ function prepareLayoutPositions(nodes, nodeIndices, layout) {
2493
2493
  };
2494
2494
  });
2495
2495
  }
2496
+ /**
2497
+ * Drags one node with the pointer. A press on a node arms a gesture; once the pointer leaves the
2498
+ * click tolerance (a finger must rest on the node first) the node follows it until release.
2499
+ * Camera controls are switched off for the gesture and restored when it ends, so a press that stays
2500
+ * a click behaves as before. Escape, a cancelled pointer or lost pointer capture put the node back.
2501
+ */
2502
+ var NodeDragController = class {
2503
+ host;
2504
+ holdMs;
2505
+ enabled = false;
2506
+ gesture = null;
2507
+ unlisten;
2508
+ unlistenKeys = null;
2509
+ constructor(host, holdMs = 350) {
2510
+ this.host = host;
2511
+ this.holdMs = holdMs;
2512
+ this.unlisten = [
2513
+ listen(host.element, "pointerdown", this.handleDown, { capture: true }),
2514
+ listen(host.element, "pointermove", this.handleMove),
2515
+ listen(host.element, "pointerup", this.handleUp),
2516
+ listen(host.element, "pointercancel", this.handleCancel),
2517
+ listen(host.element, "lostpointercapture", this.handleCancel)
2518
+ ];
2519
+ }
2520
+ /** True from the first drag step until the node is dropped or the drag is cancelled. */
2521
+ get active() {
2522
+ return this.gesture?.phase === "dragging";
2523
+ }
2524
+ setEnabled(enabled) {
2525
+ if (!enabled) this.cancel({ restore: true });
2526
+ this.enabled = enabled;
2527
+ }
2528
+ /** Ends a running gesture as cancelled. `restore` puts the node back where it was grabbed. */
2529
+ cancel({ restore }) {
2530
+ const gesture = this.gesture;
2531
+ if (!gesture) return;
2532
+ if (gesture.phase === "dragging") {
2533
+ const restored = restore && this.host.nodePosition(gesture.nodeId) !== null;
2534
+ if (restored) this.host.moveNode(gesture.nodeId, gesture.origin);
2535
+ this.finish(gesture);
2536
+ this.host.emit({
2537
+ nodeId: gesture.nodeId,
2538
+ phase: "cancel",
2539
+ position: restored ? gesture.origin : gesture.last
2540
+ });
2541
+ return;
2542
+ }
2543
+ this.finish(gesture);
2544
+ }
2545
+ dispose() {
2546
+ this.cancel({ restore: false });
2547
+ this.enabled = false;
2548
+ for (const stop of this.unlisten) stop();
2549
+ }
2550
+ handleDown = (event) => {
2551
+ if (!this.enabled || this.gesture || event.button !== 0 || !this.host.canStart()) return;
2552
+ const nodeId = this.host.pickNode(event.clientX, event.clientY);
2553
+ if (nodeId === null) return;
2554
+ const origin = this.host.nodePosition(nodeId);
2555
+ if (!origin) return;
2556
+ const surface = this.host.createSurface(origin);
2557
+ const grabbed = surface?.(event.clientX, event.clientY);
2558
+ if (!surface || !grabbed) return;
2559
+ const touch = event.pointerType === "touch";
2560
+ const gesture = {
2561
+ controlsHeld: false,
2562
+ holdTimer: null,
2563
+ last: origin,
2564
+ nodeId,
2565
+ offset: {
2566
+ x: origin.x - grabbed.x,
2567
+ y: origin.y - grabbed.y,
2568
+ z: (origin.z ?? 0) - (grabbed.z ?? 0)
2569
+ },
2570
+ origin,
2571
+ phase: "pressed",
2572
+ pointerId: event.pointerId,
2573
+ startX: event.clientX,
2574
+ startY: event.clientY,
2575
+ surface,
2576
+ touch
2577
+ };
2578
+ this.gesture = gesture;
2579
+ this.unlistenKeys = listen(this.host.document, "keydown", this.handleKey);
2580
+ if (touch) {
2581
+ gesture.holdTimer = setTimeout(() => this.beginDrag(gesture), this.holdMs);
2582
+ return;
2583
+ }
2584
+ this.holdControls(gesture);
2585
+ capture(this.host.element, event.pointerId);
2586
+ };
2587
+ handleMove = (event) => {
2588
+ const gesture = this.gesture;
2589
+ if (!gesture || event.pointerId !== gesture.pointerId) return;
2590
+ if (gesture.phase === "pressed") {
2591
+ if (Math.hypot(event.clientX - gesture.startX, event.clientY - gesture.startY) <= 5) return;
2592
+ if (gesture.touch) {
2593
+ this.finish(gesture);
2594
+ return;
2595
+ }
2596
+ this.beginDrag(gesture);
2597
+ }
2598
+ this.follow(gesture, event.clientX, event.clientY);
2599
+ };
2600
+ handleUp = (event) => {
2601
+ const gesture = this.gesture;
2602
+ if (!gesture || event.pointerId !== gesture.pointerId) return;
2603
+ this.finish(gesture);
2604
+ if (gesture.phase === "dragging") this.host.emit({
2605
+ nodeId: gesture.nodeId,
2606
+ phase: "end",
2607
+ position: gesture.last
2608
+ });
2609
+ };
2610
+ handleCancel = (event) => {
2611
+ if (this.gesture && event.pointerId === this.gesture.pointerId) this.cancel({ restore: true });
2612
+ };
2613
+ handleKey = (event) => {
2614
+ if (event.key === "Escape") this.cancel({ restore: true });
2615
+ };
2616
+ beginDrag(gesture) {
2617
+ if (this.gesture !== gesture || gesture.phase === "dragging") return;
2618
+ gesture.phase = "dragging";
2619
+ clearHold(gesture);
2620
+ this.holdControls(gesture);
2621
+ if (gesture.touch) capture(this.host.element, gesture.pointerId);
2622
+ this.host.emit({
2623
+ nodeId: gesture.nodeId,
2624
+ phase: "start",
2625
+ position: gesture.origin
2626
+ });
2627
+ }
2628
+ follow(gesture, clientX, clientY) {
2629
+ if (!this.host.nodePosition(gesture.nodeId)) {
2630
+ this.cancel({ restore: false });
2631
+ return;
2632
+ }
2633
+ const point = gesture.surface(clientX, clientY);
2634
+ if (!point) return;
2635
+ const z = point.z === void 0 ? gesture.origin.z : point.z + gesture.offset.z;
2636
+ const position = {
2637
+ x: point.x + gesture.offset.x,
2638
+ y: point.y + gesture.offset.y,
2639
+ ...z === void 0 ? {} : { z }
2640
+ };
2641
+ gesture.last = position;
2642
+ this.host.moveNode(gesture.nodeId, position);
2643
+ this.host.emit({
2644
+ nodeId: gesture.nodeId,
2645
+ phase: "move",
2646
+ position
2647
+ });
2648
+ }
2649
+ holdControls(gesture) {
2650
+ if (gesture.controlsHeld) return;
2651
+ gesture.controlsHeld = true;
2652
+ this.host.setControlsEnabled(false);
2653
+ }
2654
+ /** Drops the gesture and everything it held: timer, pointer capture, key listener, camera controls. */
2655
+ finish(gesture) {
2656
+ if (this.gesture !== gesture) return;
2657
+ this.gesture = null;
2658
+ clearHold(gesture);
2659
+ this.unlistenKeys?.();
2660
+ this.unlistenKeys = null;
2661
+ release(this.host.element, gesture.pointerId);
2662
+ if (gesture.controlsHeld) this.host.setControlsEnabled(true);
2663
+ }
2664
+ };
2665
+ /** Typed `addEventListener` for the pointer and key events this module handles; returns the unsubscribe. */
2666
+ function listen(target, type, handler, options) {
2667
+ const listener = handler;
2668
+ target.addEventListener(type, listener, options);
2669
+ return () => target.removeEventListener(type, listener, options);
2670
+ }
2671
+ function clearHold(gesture) {
2672
+ if (gesture.holdTimer === null) return;
2673
+ clearTimeout(gesture.holdTimer);
2674
+ gesture.holdTimer = null;
2675
+ }
2676
+ function capture(element, pointerId) {
2677
+ try {
2678
+ element.setPointerCapture(pointerId);
2679
+ } catch {}
2680
+ }
2681
+ function release(element, pointerId) {
2682
+ try {
2683
+ element.releasePointerCapture(pointerId);
2684
+ } catch {}
2685
+ }
2686
+ //#endregion
2687
+ //#region src/node-drag-surface.ts
2688
+ /**
2689
+ * Where the pointer is in world space on the plane a dragged node moves in. 2D unprojects onto the
2690
+ * view plane and reports no depth. 3D intersects the pointer ray with a plane through the anchor
2691
+ * that faces the camera as it was at the press, so the node keeps its depth and follows the pointer
2692
+ * across the screen. Returns `null` for an empty canvas, and from the surface when the ray misses.
2693
+ */
2694
+ function createNodeDragSurface({ anchor, bounds, camera, raycaster }) {
2695
+ const initial = bounds();
2696
+ if (initial.width === 0 || initial.height === 0) return null;
2697
+ const ndc = new Vector2();
2698
+ const toNdc = (clientX, clientY) => {
2699
+ const { height, left, top, width } = bounds();
2700
+ return ndc.set((clientX - left) / width * 2 - 1, -((clientY - top) / height) * 2 + 1);
2701
+ };
2702
+ if (camera.type === "OrthographicCamera") return (clientX, clientY) => {
2703
+ const point = toNdc(clientX, clientY);
2704
+ const world = new Vector3(point.x, point.y, 0).unproject(camera);
2705
+ return {
2706
+ x: world.x,
2707
+ y: world.y
2708
+ };
2709
+ };
2710
+ const plane = new Plane().setFromNormalAndCoplanarPoint(camera.getWorldDirection(new Vector3()), new Vector3(anchor.x, anchor.y, anchor.z ?? 0));
2711
+ const hit = new Vector3();
2712
+ return (clientX, clientY) => {
2713
+ raycaster.setFromCamera(toNdc(clientX, clientY), camera);
2714
+ return raycaster.ray.intersectPlane(plane, hit) ? {
2715
+ x: hit.x,
2716
+ y: hit.y,
2717
+ z: hit.z
2718
+ } : null;
2719
+ };
2720
+ }
2496
2721
  const vertexShader$1 = `
2497
2722
  attribute float instanceGlowOpacity;
2498
2723
  varying vec3 glowColor;
@@ -3386,6 +3611,8 @@ var Graphraum = class {
3386
3611
  gpuTimer = null;
3387
3612
  viewListeners = /* @__PURE__ */ new Set();
3388
3613
  focusListeners = /* @__PURE__ */ new Set();
3614
+ nodeDragListeners = /* @__PURE__ */ new Set();
3615
+ nodeDrag;
3389
3616
  lastHoveredNodeId = null;
3390
3617
  antialias;
3391
3618
  depthOfField = DEPTH_OF_FIELD_DEFAULTS;
@@ -3433,6 +3660,7 @@ var Graphraum = class {
3433
3660
  this.renderer.domElement.addEventListener("pointermove", this.handlePointerMove);
3434
3661
  this.renderer.domElement.addEventListener("pointerleave", this.handlePointerLeave);
3435
3662
  for (const type of FLIGHT_CANCEL_EVENTS) this.renderer.domElement.addEventListener(type, this.cancelFlight);
3663
+ this.nodeDrag = new NodeDragController(this.createNodeDragHost());
3436
3664
  this.camera = this.createCamera();
3437
3665
  this.controls = this.createControls();
3438
3666
  this.resizeObserver = new ResizeObserver(() => this.resize());
@@ -3586,6 +3814,7 @@ var Graphraum = class {
3586
3814
  this.requestRender();
3587
3815
  }
3588
3816
  replaceData(data, fitView) {
3817
+ this.nodeDrag.cancel({ restore: false });
3589
3818
  const compiled = compileGraph(data, this.visuals);
3590
3819
  this.disposeGraphObjects();
3591
3820
  this.data = {
@@ -3903,6 +4132,7 @@ var Graphraum = class {
3903
4132
  setMode(mode) {
3904
4133
  if (mode === this.mode) return;
3905
4134
  this.cancelFlight();
4135
+ this.nodeDrag.cancel({ restore: true });
3906
4136
  this.stopAutoOrbit();
3907
4137
  this.mode = mode;
3908
4138
  this.controls.removeEventListener("change", this.handleViewChange);
@@ -4030,6 +4260,51 @@ var Graphraum = class {
4030
4260
  listener();
4031
4261
  return () => this.viewListeners.delete(listener);
4032
4262
  }
4263
+ /**
4264
+ * Turns node dragging on or off (default off). A drag starts when the pointer moves past a small
4265
+ * tolerance after a press on a node (a finger must rest on the node first, so a swipe still pans
4266
+ * the camera), moves the node on a plane facing the camera, and ends on release. Escape or a
4267
+ * cancelled pointer put the node back. Not available while the density LOD shows clusters.
4268
+ */
4269
+ setNodeDrag(options) {
4270
+ if (typeof options?.enabled !== "boolean") throw new Error("setNodeDrag option enabled must be a boolean.");
4271
+ this.nodeDrag.setEnabled(options.enabled);
4272
+ }
4273
+ /** Subscribes to node drag steps (`start`, `move`, `end`, `cancel`). Returns an unsubscribe function. */
4274
+ onNodeDrag(listener) {
4275
+ this.nodeDragListeners.add(listener);
4276
+ return () => this.nodeDragListeners.delete(listener);
4277
+ }
4278
+ createNodeDragHost() {
4279
+ const element = this.renderer.domElement;
4280
+ return {
4281
+ canStart: () => !this.destroyed && this.nodeMesh !== null && !this.densityLodActive,
4282
+ createSurface: (anchor) => createNodeDragSurface({
4283
+ anchor,
4284
+ bounds: () => element.getBoundingClientRect(),
4285
+ camera: this.camera,
4286
+ raycaster: this.raycaster
4287
+ }),
4288
+ document: element.ownerDocument,
4289
+ element,
4290
+ emit: (event) => {
4291
+ for (const listener of this.nodeDragListeners) listener(event);
4292
+ },
4293
+ moveNode: (nodeId, position) => this.updateNodes([{
4294
+ id: nodeId,
4295
+ position
4296
+ }]),
4297
+ nodePosition: (nodeId) => {
4298
+ const index = this.nodeIndices.get(nodeId);
4299
+ const node = index === void 0 ? void 0 : this.data.nodes[index];
4300
+ return node ? { ...node.position } : null;
4301
+ },
4302
+ pickNode: (clientX, clientY) => this.pickNode(clientX, clientY),
4303
+ setControlsEnabled: (enabled) => {
4304
+ this.controls.enabled = enabled;
4305
+ }
4306
+ };
4307
+ }
4033
4308
  /** Subscribes to selection and hover changes that affect overlay focus chrome. */
4034
4309
  onFocusChange(listener) {
4035
4310
  this.focusListeners.add(listener);
@@ -4040,6 +4315,7 @@ var Graphraum = class {
4040
4315
  for (const listener of this.focusListeners) listener();
4041
4316
  }
4042
4317
  handlePointerMove = (event) => {
4318
+ if (this.nodeDrag.active) return;
4043
4319
  const nodeId = this.pickNode(event.clientX, event.clientY);
4044
4320
  if (nodeId === this.lastHoveredNodeId) return;
4045
4321
  this.lastHoveredNodeId = nodeId;
@@ -4396,6 +4672,7 @@ var Graphraum = class {
4396
4672
  destroy() {
4397
4673
  this.destroyed = true;
4398
4674
  this.cancelFlight();
4675
+ this.nodeDrag.dispose();
4399
4676
  this.stopAutoOrbit();
4400
4677
  this.resizeObserver.disconnect();
4401
4678
  this.renderer.domElement.removeEventListener("pointermove", this.handlePointerMove);
@@ -4540,6 +4817,7 @@ var Graphraum = class {
4540
4817
  * without a loop.
4541
4818
  */
4542
4819
  stepAutoOrbit = (deltaSeconds) => {
4820
+ if (this.nodeDrag.active) return;
4543
4821
  try {
4544
4822
  this.controls.autoRotate = true;
4545
4823
  this.orbitUpdating = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yggy-tech/graphraum",
3
- "version": "0.36.0",
3
+ "version": "0.37.0",
4
4
  "description": "yggy's WebGL engine for interactive graphs in 2D and 3D",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",