@cocorof/graphier 0.1.0 → 1.1.1

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.cjs CHANGED
@@ -1721,27 +1721,31 @@ function applyNodeHighlight(nodesMesh, nodes, selectedNodeId, highlightSet, them
1721
1721
  } else if (node.id === selectedNodeId) {
1722
1722
  tmpColor.set(brightColor);
1723
1723
  brightTmp.setRGB(1, 1, 1);
1724
- tmpColor.lerp(brightTmp, 0.5);
1724
+ tmpColor.lerp(brightTmp, 0.65);
1725
+ tmpColor.multiplyScalar(1.4);
1726
+ tmpColor.r = Math.min(tmpColor.r, 2);
1727
+ tmpColor.g = Math.min(tmpColor.g, 2);
1728
+ tmpColor.b = Math.min(tmpColor.b, 2);
1725
1729
  } else if (highlightSet.has(node.id)) {
1726
1730
  const hop = highlightSet.get(node.id);
1727
1731
  if (hop === 1) {
1728
- tmpColor.set(brightColor);
1732
+ tmpColor.set(brightColor).multiplyScalar(1.2);
1729
1733
  } else if (hop === 2) {
1730
1734
  tmpColor.set(baseColor);
1731
1735
  brightTmp.set(brightColor);
1732
- tmpColor.lerp(brightTmp, 0.4);
1736
+ tmpColor.lerp(brightTmp, 0.5);
1733
1737
  } else {
1734
- tmpColor.set(baseColor).multiplyScalar(0.7);
1738
+ tmpColor.set(baseColor).multiplyScalar(0.6);
1735
1739
  }
1736
1740
  } else {
1737
- tmpColor.set(baseColor).multiplyScalar(0.12);
1741
+ tmpColor.set(baseColor).multiplyScalar(0.04);
1738
1742
  }
1739
1743
  nodesMesh.setColorAt(i, tmpColor);
1740
1744
  }
1741
1745
  if (nodesMesh.instanceColor) nodesMesh.instanceColor.needsUpdate = true;
1742
1746
  const mat = nodesMesh.material;
1743
1747
  if ((_a = mat.uniforms) == null ? void 0 : _a.uGlowIntensity) {
1744
- mat.uniforms.uGlowIntensity.value = highlightSet ? 1.15 : 1;
1748
+ mat.uniforms.uGlowIntensity.value = highlightSet ? 1.5 : 1;
1745
1749
  }
1746
1750
  }
1747
1751
  function applyEdgeHighlight(edgesMesh, links, edgeNodeIndices, edgeLinkIndices, highlightSet, edgeOpacity, nodeCount, theme) {
@@ -1769,7 +1773,7 @@ function applyEdgeHighlight(edgesMesh, links, edgeNodeIndices, edgeLinkIndices,
1769
1773
  tmpColor.multiplyScalar(1.2);
1770
1774
  }
1771
1775
  } else {
1772
- tmpColor.setRGB(0.03, 0.03, 0.06);
1776
+ tmpColor.setRGB(0.01, 0.01, 0.02);
1773
1777
  }
1774
1778
  colorArr[i * 6 + 0] = tmpColor.r;
1775
1779
  colorArr[i * 6 + 1] = tmpColor.g;
@@ -1856,6 +1860,9 @@ function createLabelSystem(maxLabels) {
1856
1860
  }
1857
1861
  function defaultLabelText$1(node) {
1858
1862
  const label = node.label ?? node.id ?? "";
1863
+ if (node.type === "repo" && label.includes("/")) {
1864
+ return label.split("/").pop();
1865
+ }
1859
1866
  return label.length > 30 ? label.substring(0, 27) + "…" : label;
1860
1867
  }
1861
1868
  function getOrCreateTexture(cache, nodeId, text, color) {
@@ -1894,7 +1901,7 @@ function getOrCreateTexture(cache, nodeId, text, color) {
1894
1901
  }
1895
1902
  return entry;
1896
1903
  }
1897
- function updateLabels(state, nodes, positions, scales, camera, theme, showLabels, labelScale, labelThreshold, maxLabels, labelFormatter) {
1904
+ function updateLabels(state, nodes, positions, scales, camera, theme, showLabels, labelScale, labelThreshold, maxLabels, labelFormatter, highlightSet) {
1898
1905
  if (!positions || nodes.length === 0 || !showLabels) {
1899
1906
  for (const sp of state.sprites) sp.visible = false;
1900
1907
  return;
@@ -1908,6 +1915,7 @@ function updateLabels(state, nodes, positions, scales, camera, theme, showLabels
1908
1915
  const maxDistSq = maxDist * maxDist;
1909
1916
  const candidates = [];
1910
1917
  for (let i = 0; i < nc; i++) {
1918
+ if (highlightSet && !highlightSet.has(nodes[i].id)) continue;
1911
1919
  const dx = positions[i * 3] - camPos.x;
1912
1920
  const dy = positions[i * 3 + 1] - camPos.y;
1913
1921
  const dz = positions[i * 3 + 2] - camPos.z;
@@ -1953,58 +1961,48 @@ function disposeLabelSystem(state) {
1953
1961
  }
1954
1962
  state.textureCache.clear();
1955
1963
  }
1956
- const ACCEL = 12e-4;
1957
- const MAX_SPEED = 0.06;
1958
- const DAMPING = 0.88;
1959
- const ZOOM_FACTOR = 0.015;
1960
- const DEAD_ZONE = 1e-4;
1964
+ const ORBIT_ACCEL = 12e-4;
1965
+ const ORBIT_MAX_SPEED = 0.06;
1966
+ const ORBIT_DAMPING = 0.88;
1967
+ const ORBIT_ZOOM_FACTOR = 0.015;
1968
+ const ORBIT_DEAD_ZONE = 1e-4;
1969
+ const FLY_ACCEL = 6e-3;
1970
+ const FLY_FRICTION = 0.92;
1971
+ const FLY_MAX_THRUST = 0.18;
1972
+ const FLY_TURN_RATE = 8e-3;
1973
+ const FLY_DEAD_ZONE = 2e-4;
1961
1974
  const _offset = new THREE__namespace.Vector3();
1962
1975
  const _right = new THREE__namespace.Vector3();
1963
1976
  const _q = new THREE__namespace.Quaternion();
1964
- function setupKeyboardControls(camera, controls, container) {
1965
- const keys = {};
1977
+ const _dir = new THREE__namespace.Vector3();
1978
+ function clamp(v, min, max) {
1979
+ return v < min ? min : v > max ? max : v;
1980
+ }
1981
+ function createOrbitUpdate(camera, controls, keys) {
1966
1982
  const vel = { zoom: 0, azimuth: 0, polar: 0 };
1967
- function onKeyDown2(e) {
1968
- var _a;
1969
- const tag = (_a = e.target) == null ? void 0 : _a.tagName;
1970
- if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
1971
- keys[e.key] = true;
1972
- }
1973
- function onKeyUp(e) {
1974
- keys[e.key] = false;
1975
- }
1976
- function onBlur() {
1977
- for (const k of Object.keys(keys)) keys[k] = false;
1978
- vel.zoom = 0;
1979
- vel.azimuth = 0;
1980
- vel.polar = 0;
1981
- }
1982
- container.addEventListener("keydown", onKeyDown2);
1983
- container.addEventListener("keyup", onKeyUp);
1984
- container.addEventListener("blur", onBlur);
1985
- function update() {
1986
- if (keys["z"] || keys["Z"]) vel.zoom -= ACCEL;
1987
- if (keys["x"] || keys["X"]) vel.zoom += ACCEL;
1988
- if (keys["ArrowLeft"]) vel.azimuth -= ACCEL;
1989
- if (keys["ArrowRight"]) vel.azimuth += ACCEL;
1990
- if (keys["ArrowUp"]) vel.polar -= ACCEL;
1991
- if (keys["ArrowDown"]) vel.polar += ACCEL;
1992
- vel.zoom = clamp(vel.zoom, -MAX_SPEED, MAX_SPEED);
1993
- vel.azimuth = clamp(vel.azimuth, -MAX_SPEED, MAX_SPEED);
1994
- vel.polar = clamp(vel.polar, -MAX_SPEED, MAX_SPEED);
1995
- vel.zoom *= DAMPING;
1996
- vel.azimuth *= DAMPING;
1997
- vel.polar *= DAMPING;
1998
- if (Math.abs(vel.zoom) < DEAD_ZONE) vel.zoom = 0;
1999
- if (Math.abs(vel.azimuth) < DEAD_ZONE) vel.azimuth = 0;
2000
- if (Math.abs(vel.polar) < DEAD_ZONE) vel.polar = 0;
1983
+ return function update() {
1984
+ if (keys["z"] || keys["Z"]) vel.zoom -= ORBIT_ACCEL;
1985
+ if (keys["x"] || keys["X"]) vel.zoom += ORBIT_ACCEL;
1986
+ if (keys["ArrowLeft"]) vel.azimuth -= ORBIT_ACCEL;
1987
+ if (keys["ArrowRight"]) vel.azimuth += ORBIT_ACCEL;
1988
+ if (keys["ArrowUp"]) vel.polar -= ORBIT_ACCEL;
1989
+ if (keys["ArrowDown"]) vel.polar += ORBIT_ACCEL;
1990
+ vel.zoom = clamp(vel.zoom, -ORBIT_MAX_SPEED, ORBIT_MAX_SPEED);
1991
+ vel.azimuth = clamp(vel.azimuth, -ORBIT_MAX_SPEED, ORBIT_MAX_SPEED);
1992
+ vel.polar = clamp(vel.polar, -ORBIT_MAX_SPEED, ORBIT_MAX_SPEED);
1993
+ vel.zoom *= ORBIT_DAMPING;
1994
+ vel.azimuth *= ORBIT_DAMPING;
1995
+ vel.polar *= ORBIT_DAMPING;
1996
+ if (Math.abs(vel.zoom) < ORBIT_DEAD_ZONE) vel.zoom = 0;
1997
+ if (Math.abs(vel.azimuth) < ORBIT_DEAD_ZONE) vel.azimuth = 0;
1998
+ if (Math.abs(vel.polar) < ORBIT_DEAD_ZONE) vel.polar = 0;
2001
1999
  const hasMotion = vel.zoom !== 0 || vel.azimuth !== 0 || vel.polar !== 0;
2002
2000
  if (!hasMotion) return false;
2003
2001
  const target = controls.target;
2004
2002
  _offset.copy(camera.position).sub(target);
2005
2003
  let radius = _offset.length();
2006
2004
  if (vel.zoom !== 0) {
2007
- radius *= 1 + vel.zoom * ZOOM_FACTOR * radius * 0.01;
2005
+ radius *= 1 + vel.zoom * ORBIT_ZOOM_FACTOR * radius * 0.01;
2008
2006
  radius = Math.max(controls.minDistance, Math.min(controls.maxDistance, radius));
2009
2007
  }
2010
2008
  if (vel.azimuth !== 0) {
@@ -2025,16 +2023,72 @@ function setupKeyboardControls(camera, controls, container) {
2025
2023
  camera.lookAt(target);
2026
2024
  controls.target.copy(target);
2027
2025
  return true;
2026
+ };
2027
+ }
2028
+ function createFlyUpdate(camera, controls, keys) {
2029
+ const vel = { thrust: 0, yaw: 0, pitch: 0 };
2030
+ return function update() {
2031
+ if (keys["z"] || keys["Z"]) vel.thrust += FLY_ACCEL;
2032
+ if (keys["x"] || keys["X"]) vel.thrust -= FLY_ACCEL;
2033
+ if (keys["a"] || keys["A"] || keys["ArrowLeft"]) vel.yaw += FLY_TURN_RATE;
2034
+ if (keys["d"] || keys["D"] || keys["ArrowRight"]) vel.yaw -= FLY_TURN_RATE;
2035
+ if (keys["w"] || keys["W"] || keys["ArrowUp"]) vel.pitch += FLY_TURN_RATE;
2036
+ if (keys["s"] || keys["S"] || keys["ArrowDown"]) vel.pitch -= FLY_TURN_RATE;
2037
+ const lookDist = camera.position.distanceTo(controls.target);
2038
+ const distScale = Math.max(0.3, lookDist * 3e-3);
2039
+ vel.thrust = clamp(vel.thrust, -FLY_MAX_THRUST * distScale, FLY_MAX_THRUST * distScale);
2040
+ vel.yaw = clamp(vel.yaw, -FLY_TURN_RATE * 2, FLY_TURN_RATE * 2);
2041
+ vel.pitch = clamp(vel.pitch, -FLY_TURN_RATE * 2, FLY_TURN_RATE * 2);
2042
+ vel.thrust *= FLY_FRICTION;
2043
+ vel.yaw *= FLY_FRICTION;
2044
+ vel.pitch *= FLY_FRICTION;
2045
+ if (Math.abs(vel.thrust) < FLY_DEAD_ZONE) vel.thrust = 0;
2046
+ if (Math.abs(vel.yaw) < FLY_DEAD_ZONE) vel.yaw = 0;
2047
+ if (Math.abs(vel.pitch) < FLY_DEAD_ZONE) vel.pitch = 0;
2048
+ const hasMotion = vel.thrust !== 0 || vel.yaw !== 0 || vel.pitch !== 0;
2049
+ if (!hasMotion) return false;
2050
+ if (vel.yaw !== 0) {
2051
+ _q.setFromAxisAngle(camera.up, vel.yaw);
2052
+ camera.quaternion.premultiply(_q);
2053
+ }
2054
+ if (vel.pitch !== 0) {
2055
+ _right.setFromMatrixColumn(camera.matrixWorld, 0).normalize();
2056
+ _q.setFromAxisAngle(_right, vel.pitch);
2057
+ camera.quaternion.premultiply(_q);
2058
+ }
2059
+ if (vel.thrust !== 0) {
2060
+ camera.getWorldDirection(_dir);
2061
+ _dir.multiplyScalar(vel.thrust * distScale * 100);
2062
+ camera.position.add(_dir);
2063
+ controls.target.add(_dir);
2064
+ }
2065
+ return true;
2066
+ };
2067
+ }
2068
+ function setupKeyboardControls(camera, controls, container, mode = "fly") {
2069
+ const keys = {};
2070
+ function onKeyDown2(e) {
2071
+ var _a;
2072
+ const tag = (_a = e.target) == null ? void 0 : _a.tagName;
2073
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
2074
+ keys[e.key] = true;
2075
+ }
2076
+ function onKeyUp(e) {
2077
+ keys[e.key] = false;
2078
+ }
2079
+ function onBlur() {
2080
+ for (const k of Object.keys(keys)) keys[k] = false;
2028
2081
  }
2082
+ container.addEventListener("keydown", onKeyDown2);
2083
+ container.addEventListener("keyup", onKeyUp);
2084
+ container.addEventListener("blur", onBlur);
2085
+ const updateFn = mode === "fly" ? createFlyUpdate(camera, controls, keys) : createOrbitUpdate(camera, controls, keys);
2029
2086
  function cleanup() {
2030
2087
  container.removeEventListener("keydown", onKeyDown2);
2031
2088
  container.removeEventListener("keyup", onKeyUp);
2032
2089
  container.removeEventListener("blur", onBlur);
2033
2090
  }
2034
- return { update, cleanup };
2035
- }
2036
- function clamp(v, min, max) {
2037
- return v < min ? min : v > max ? max : v;
2091
+ return { update: updateFn, cleanup };
2038
2092
  }
2039
2093
  function createScene(container, backgroundColor, style, rendererConfig) {
2040
2094
  const scene = new THREE__namespace.Scene();
@@ -2071,7 +2125,8 @@ function createScene(container, backgroundColor, style, rendererConfig) {
2071
2125
  scene.add(selectionRing);
2072
2126
  const labels = createLabelSystem(style.maxLabels);
2073
2127
  scene.add(labels.group);
2074
- const keyboard = setupKeyboardControls(camera, controls, container);
2128
+ const cameraMode = (rendererConfig == null ? void 0 : rendererConfig.cameraMode) ?? "fly";
2129
+ const keyboard = setupKeyboardControls(camera, controls, container, cameraMode);
2075
2130
  return {
2076
2131
  scene,
2077
2132
  camera,
@@ -2410,21 +2465,136 @@ function zoomToFitPositions(camera, controls, positions, duration, padding) {
2410
2465
  duration
2411
2466
  );
2412
2467
  }
2413
- function setupInteraction(canvas, camera, controls, getNodesMesh, getNodes, callbacks, container) {
2468
+ const EDGE_HIT_THRESHOLD = 5;
2469
+ function setupInteraction(canvas, camera, controls, getNodesMesh, getNodes, callbacks, container, getEdgeData) {
2414
2470
  const raycaster = new THREE__namespace.Raycaster();
2415
2471
  const mouse = new THREE__namespace.Vector2();
2416
2472
  let mouseDownPos = null;
2417
2473
  let lastClickTime = 0;
2418
2474
  let lastClickNodeId = null;
2419
2475
  let singleClickTimer = null;
2476
+ let dragNode = null;
2477
+ let isDragging = false;
2478
+ const dragPlane = new THREE__namespace.Plane();
2479
+ const dragIntersect = new THREE__namespace.Vector3();
2420
2480
  function onPointerDown2(e) {
2421
2481
  mouseDownPos = { x: e.clientX, y: e.clientY };
2422
2482
  if (container && document.activeElement !== container) {
2423
2483
  container.focus({ preventScroll: true });
2424
2484
  }
2485
+ if (callbacks.onNodeDrag) {
2486
+ const nodesMesh = getNodesMesh();
2487
+ if (nodesMesh) {
2488
+ const rect = canvas.getBoundingClientRect();
2489
+ mouse.x = (e.clientX - rect.left) / rect.width * 2 - 1;
2490
+ mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
2491
+ raycaster.setFromCamera(mouse, camera);
2492
+ const hits = raycaster.intersectObject(nodesMesh);
2493
+ if (hits.length > 0 && hits[0].instanceId != null) {
2494
+ const nodes = getNodes();
2495
+ dragNode = nodes[hits[0].instanceId] ?? null;
2496
+ hits[0].instanceId;
2497
+ }
2498
+ }
2499
+ }
2500
+ }
2501
+ function onPointerMove2(e) {
2502
+ var _a, _b, _c;
2503
+ if (dragNode && mouseDownPos && callbacks.onNodeDrag) {
2504
+ const dx = Math.abs(e.clientX - mouseDownPos.x);
2505
+ const dy = Math.abs(e.clientY - mouseDownPos.y);
2506
+ if (dx + dy > 5) {
2507
+ isDragging = true;
2508
+ controls.enabled = false;
2509
+ const rect2 = canvas.getBoundingClientRect();
2510
+ mouse.x = (e.clientX - rect2.left) / rect2.width * 2 - 1;
2511
+ mouse.y = -((e.clientY - rect2.top) / rect2.height) * 2 + 1;
2512
+ const nodePos = new THREE__namespace.Vector3(
2513
+ dragNode.x ?? 0,
2514
+ dragNode.y ?? 0,
2515
+ dragNode.z ?? 0
2516
+ );
2517
+ dragPlane.setFromNormalAndCoplanarPoint(
2518
+ camera.getWorldDirection(new THREE__namespace.Vector3()).negate(),
2519
+ nodePos
2520
+ );
2521
+ raycaster.setFromCamera(mouse, camera);
2522
+ if (raycaster.ray.intersectPlane(dragPlane, dragIntersect)) {
2523
+ callbacks.onNodeDrag(dragNode, {
2524
+ x: dragIntersect.x,
2525
+ y: dragIntersect.y,
2526
+ z: dragIntersect.z
2527
+ });
2528
+ }
2529
+ return;
2530
+ }
2531
+ }
2532
+ if (!callbacks.onNodeHover && !callbacks.onLinkHover) return;
2533
+ const nodesMesh = getNodesMesh();
2534
+ if (!nodesMesh) return;
2535
+ const rect = canvas.getBoundingClientRect();
2536
+ mouse.x = (e.clientX - rect.left) / rect.width * 2 - 1;
2537
+ mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
2538
+ raycaster.setFromCamera(mouse, camera);
2539
+ const hits = raycaster.intersectObject(nodesMesh);
2540
+ if (hits.length > 0 && hits[0].instanceId != null) {
2541
+ const nodes = getNodes();
2542
+ const node = nodes[hits[0].instanceId];
2543
+ if (node && node.id !== lastHoveredId) {
2544
+ lastHoveredId = node.id;
2545
+ lastHoveredLinkIdx = -1;
2546
+ canvas.style.cursor = "pointer";
2547
+ (_a = callbacks.onNodeHover) == null ? void 0 : _a.call(callbacks, node);
2548
+ (_b = callbacks.onLinkHover) == null ? void 0 : _b.call(callbacks, null);
2549
+ }
2550
+ } else {
2551
+ if (lastHoveredId !== null) {
2552
+ lastHoveredId = null;
2553
+ canvas.style.cursor = "default";
2554
+ (_c = callbacks.onNodeHover) == null ? void 0 : _c.call(callbacks, null);
2555
+ }
2556
+ if (callbacks.onLinkHover && getEdgeData) {
2557
+ const edgeData = getEdgeData();
2558
+ if (edgeData == null ? void 0 : edgeData.positions) {
2559
+ const hitLink = findNearestEdge(
2560
+ e.clientX,
2561
+ e.clientY,
2562
+ rect,
2563
+ camera,
2564
+ edgeData
2565
+ );
2566
+ if (hitLink) {
2567
+ if (lastHoveredLinkIdx !== hitLink.index) {
2568
+ lastHoveredLinkIdx = hitLink.index;
2569
+ canvas.style.cursor = "pointer";
2570
+ callbacks.onLinkHover(hitLink.link);
2571
+ }
2572
+ } else if (lastHoveredLinkIdx !== -1) {
2573
+ lastHoveredLinkIdx = -1;
2574
+ canvas.style.cursor = "default";
2575
+ callbacks.onLinkHover(null);
2576
+ }
2577
+ }
2578
+ }
2579
+ }
2425
2580
  }
2426
2581
  function onPointerUp2(e) {
2427
2582
  var _a;
2583
+ if (isDragging && dragNode && callbacks.onNodeDragEnd) {
2584
+ callbacks.onNodeDragEnd(dragNode, {
2585
+ x: dragNode.x ?? 0,
2586
+ y: dragNode.y ?? 0,
2587
+ z: dragNode.z ?? 0
2588
+ });
2589
+ }
2590
+ if (isDragging) {
2591
+ isDragging = false;
2592
+ dragNode = null;
2593
+ controls.enabled = true;
2594
+ mouseDownPos = null;
2595
+ return;
2596
+ }
2597
+ dragNode = null;
2428
2598
  if (!mouseDownPos) return;
2429
2599
  const dx = Math.abs(e.clientX - mouseDownPos.x);
2430
2600
  const dy = Math.abs(e.clientY - mouseDownPos.y);
@@ -2472,6 +2642,22 @@ function setupInteraction(canvas, camera, controls, getNodesMesh, getNodes, call
2472
2642
  callbacks.onNodeClick(node);
2473
2643
  }, 250);
2474
2644
  } else {
2645
+ if (callbacks.onLinkClick && getEdgeData) {
2646
+ const edgeData = getEdgeData();
2647
+ if (edgeData == null ? void 0 : edgeData.positions) {
2648
+ const hitLink = findNearestEdge(
2649
+ e.clientX,
2650
+ e.clientY,
2651
+ rect,
2652
+ camera,
2653
+ edgeData
2654
+ );
2655
+ if (hitLink) {
2656
+ callbacks.onLinkClick(hitLink.link);
2657
+ return;
2658
+ }
2659
+ }
2660
+ }
2475
2661
  if (singleClickTimer) clearTimeout(singleClickTimer);
2476
2662
  singleClickTimer = null;
2477
2663
  lastClickTime = 0;
@@ -2479,13 +2665,9 @@ function setupInteraction(canvas, camera, controls, getNodesMesh, getNodes, call
2479
2665
  callbacks.onNodeClick(null);
2480
2666
  }
2481
2667
  }
2482
- const keyTarget = container ?? window;
2483
- function onKeyDown2(e) {
2484
- if (e.key === "Escape") callbacks.onNodeClick(null);
2485
- }
2486
- let lastHoveredId = null;
2487
- function onPointerMove2(e) {
2488
- if (!callbacks.onNodeHover) return;
2668
+ function onContextMenu2(e) {
2669
+ if (!callbacks.onContextMenu) return;
2670
+ e.preventDefault();
2489
2671
  const nodesMesh = getNodesMesh();
2490
2672
  if (!nodesMesh) return;
2491
2673
  const rect = canvas.getBoundingClientRect();
@@ -2496,20 +2678,21 @@ function setupInteraction(canvas, camera, controls, getNodesMesh, getNodes, call
2496
2678
  if (hits.length > 0 && hits[0].instanceId != null) {
2497
2679
  const nodes = getNodes();
2498
2680
  const node = nodes[hits[0].instanceId];
2499
- if (node && node.id !== lastHoveredId) {
2500
- lastHoveredId = node.id;
2501
- canvas.style.cursor = "pointer";
2502
- callbacks.onNodeHover(node);
2681
+ if (node) {
2682
+ callbacks.onContextMenu(node, { x: e.clientX, y: e.clientY });
2503
2683
  }
2504
- } else if (lastHoveredId !== null) {
2505
- lastHoveredId = null;
2506
- canvas.style.cursor = "default";
2507
- callbacks.onNodeHover(null);
2508
2684
  }
2509
2685
  }
2686
+ const keyTarget = container ?? window;
2687
+ function onKeyDown2(e) {
2688
+ if (e.key === "Escape") callbacks.onNodeClick(null);
2689
+ }
2690
+ let lastHoveredId = null;
2691
+ let lastHoveredLinkIdx = -1;
2510
2692
  canvas.addEventListener("pointerdown", onPointerDown2);
2511
2693
  canvas.addEventListener("pointerup", onPointerUp2);
2512
2694
  canvas.addEventListener("pointermove", onPointerMove2);
2695
+ canvas.addEventListener("contextmenu", onContextMenu2);
2513
2696
  keyTarget.addEventListener("keydown", onKeyDown2);
2514
2697
  return {
2515
2698
  cleanup: () => {
@@ -2517,10 +2700,48 @@ function setupInteraction(canvas, camera, controls, getNodesMesh, getNodes, call
2517
2700
  canvas.removeEventListener("pointerdown", onPointerDown2);
2518
2701
  canvas.removeEventListener("pointerup", onPointerUp2);
2519
2702
  canvas.removeEventListener("pointermove", onPointerMove2);
2703
+ canvas.removeEventListener("contextmenu", onContextMenu2);
2520
2704
  keyTarget.removeEventListener("keydown", onKeyDown2);
2521
2705
  }
2522
2706
  };
2523
2707
  }
2708
+ function findNearestEdge(clientX, clientY, rect, camera, edgeData) {
2709
+ const { links, edgeNodeIndices, edgeLinkIndices, positions } = edgeData;
2710
+ const screenX = clientX - rect.left;
2711
+ const screenY = clientY - rect.top;
2712
+ const w = rect.width;
2713
+ const h = rect.height;
2714
+ let bestDist = EDGE_HIT_THRESHOLD;
2715
+ let bestIdx = -1;
2716
+ const tmpVec = new THREE__namespace.Vector3();
2717
+ for (let i = 0; i < edgeNodeIndices.length; i++) {
2718
+ const [si, ti] = edgeNodeIndices[i];
2719
+ tmpVec.set(positions[si * 3], positions[si * 3 + 1], positions[si * 3 + 2]);
2720
+ tmpVec.project(camera);
2721
+ const sx1 = (tmpVec.x * 0.5 + 0.5) * w;
2722
+ const sy1 = (-tmpVec.y * 0.5 + 0.5) * h;
2723
+ tmpVec.set(positions[ti * 3], positions[ti * 3 + 1], positions[ti * 3 + 2]);
2724
+ tmpVec.project(camera);
2725
+ const sx2 = (tmpVec.x * 0.5 + 0.5) * w;
2726
+ const sy2 = (-tmpVec.y * 0.5 + 0.5) * h;
2727
+ const dist = pointToSegmentDist(screenX, screenY, sx1, sy1, sx2, sy2);
2728
+ if (dist < bestDist) {
2729
+ bestDist = dist;
2730
+ bestIdx = i;
2731
+ }
2732
+ }
2733
+ if (bestIdx === -1) return null;
2734
+ return { link: links[edgeLinkIndices[bestIdx]], index: bestIdx };
2735
+ }
2736
+ function pointToSegmentDist(px, py, ax, ay, bx, by) {
2737
+ const dx = bx - ax;
2738
+ const dy = by - ay;
2739
+ const lenSq = dx * dx + dy * dy;
2740
+ if (lenSq < 1e-4) return Math.hypot(px - ax, py - ay);
2741
+ let t = ((px - ax) * dx + (py - ay) * dy) / lenSq;
2742
+ t = Math.max(0, Math.min(1, t));
2743
+ return Math.hypot(px - (ax + t * dx), py - (ay + t * dy));
2744
+ }
2524
2745
  function disposeObject(obj) {
2525
2746
  if (!obj) return;
2526
2747
  const mesh = obj;
@@ -2542,6 +2763,13 @@ function NetworkGraph3DInner(props, ref) {
2542
2763
  onNodeClick,
2543
2764
  onNodeDoubleClick,
2544
2765
  onNodeHover,
2766
+ onContextMenu: onContextMenu2,
2767
+ onLinkClick,
2768
+ onLinkHover,
2769
+ onNodeDrag,
2770
+ onNodeDragEnd,
2771
+ onLayoutSettled,
2772
+ onLayoutTick,
2545
2773
  selectedNodeId = null,
2546
2774
  highlightHops = 3,
2547
2775
  theme: themeProp,
@@ -2574,13 +2802,28 @@ function NetworkGraph3DInner(props, ref) {
2574
2802
  const onNodeClickRef = react.useRef(onNodeClick);
2575
2803
  const onNodeDoubleClickRef = react.useRef(onNodeDoubleClick);
2576
2804
  const onNodeHoverRef = react.useRef(onNodeHover);
2805
+ const onContextMenuRef = react.useRef(onContextMenu2);
2806
+ const onLinkClickRef = react.useRef(onLinkClick);
2807
+ const onLinkHoverRef = react.useRef(onLinkHover);
2808
+ const onNodeDragRef = react.useRef(onNodeDrag);
2809
+ const onNodeDragEndRef = react.useRef(onNodeDragEnd);
2810
+ const onLayoutSettledRef = react.useRef(onLayoutSettled);
2811
+ const onLayoutTickRef = react.useRef(onLayoutTick);
2577
2812
  const styleRef = react.useRef(resolvedStyle);
2578
2813
  const themeRef = react.useRef(resolvedTheme);
2579
2814
  const selectedNodeIdRef = react.useRef(selectedNodeId);
2580
2815
  const labelFormatterRef = react.useRef(labelFormatter);
2816
+ const highlightSetRef = react.useRef(null);
2581
2817
  onNodeClickRef.current = onNodeClick;
2582
2818
  onNodeDoubleClickRef.current = onNodeDoubleClick;
2583
2819
  onNodeHoverRef.current = onNodeHover;
2820
+ onContextMenuRef.current = onContextMenu2;
2821
+ onLinkClickRef.current = onLinkClick;
2822
+ onLinkHoverRef.current = onLinkHover;
2823
+ onNodeDragRef.current = onNodeDrag;
2824
+ onNodeDragEndRef.current = onNodeDragEnd;
2825
+ onLayoutSettledRef.current = onLayoutSettled;
2826
+ onLayoutTickRef.current = onLayoutTick;
2584
2827
  styleRef.current = resolvedStyle;
2585
2828
  themeRef.current = resolvedTheme;
2586
2829
  selectedNodeIdRef.current = selectedNodeId;
@@ -2646,7 +2889,8 @@ function NetworkGraph3DInner(props, ref) {
2646
2889
  styleRef.current.labelScale,
2647
2890
  styleRef.current.labelThreshold,
2648
2891
  styleRef.current.maxLabels,
2649
- labelFormatterRef.current
2892
+ labelFormatterRef.current,
2893
+ highlightSetRef.current
2650
2894
  );
2651
2895
  });
2652
2896
  const resizeObserver = setupResize(container, state);
@@ -2671,9 +2915,52 @@ function NetworkGraph3DInner(props, ref) {
2671
2915
  onNodeHover: (node) => {
2672
2916
  var _a;
2673
2917
  return (_a = onNodeHoverRef.current) == null ? void 0 : _a.call(onNodeHoverRef, node);
2918
+ },
2919
+ onContextMenu: (node, pos) => {
2920
+ var _a;
2921
+ return (_a = onContextMenuRef.current) == null ? void 0 : _a.call(onContextMenuRef, node, pos);
2922
+ },
2923
+ onLinkClick: (link) => {
2924
+ var _a;
2925
+ return (_a = onLinkClickRef.current) == null ? void 0 : _a.call(onLinkClickRef, link);
2926
+ },
2927
+ onLinkHover: (link) => {
2928
+ var _a;
2929
+ return (_a = onLinkHoverRef.current) == null ? void 0 : _a.call(onLinkHoverRef, link);
2930
+ },
2931
+ onNodeDrag: (node, pos) => {
2932
+ var _a;
2933
+ (_a = onNodeDragRef.current) == null ? void 0 : _a.call(onNodeDragRef, node, pos);
2934
+ const d = dataRef.current;
2935
+ const idx = d.nodeIdToIndex.get(node.id);
2936
+ if (idx !== void 0 && d.positions) {
2937
+ d.positions[idx * 3] = pos.x;
2938
+ d.positions[idx * 3 + 1] = pos.y;
2939
+ d.positions[idx * 3 + 2] = pos.z;
2940
+ node.x = pos.x;
2941
+ node.y = pos.y;
2942
+ node.z = pos.z;
2943
+ const gObj = graphObjRef.current;
2944
+ if (gObj && d.scales) {
2945
+ updateNodePositions(gObj.nodesMesh, d.positions, d.scales, d.nodes.length);
2946
+ }
2947
+ }
2948
+ },
2949
+ onNodeDragEnd: (node, pos) => {
2950
+ var _a;
2951
+ return (_a = onNodeDragEndRef.current) == null ? void 0 : _a.call(onNodeDragEndRef, node, pos);
2674
2952
  }
2675
2953
  },
2676
- container
2954
+ container,
2955
+ () => {
2956
+ const d = dataRef.current;
2957
+ return {
2958
+ links: d.links,
2959
+ edgeNodeIndices: d.edgeNodeIndices,
2960
+ edgeLinkIndices: d.edgeLinkIndices,
2961
+ positions: d.positions
2962
+ };
2963
+ }
2677
2964
  );
2678
2965
  return () => {
2679
2966
  cancelAnim();
@@ -2813,6 +3100,7 @@ function NetworkGraph3DInner(props, ref) {
2813
3100
  console.error("Graphier: Layout worker error:", err);
2814
3101
  };
2815
3102
  worker.onmessage = (e) => {
3103
+ var _a, _b;
2816
3104
  const msg = e.data;
2817
3105
  if (msg.type === "positions") {
2818
3106
  const positions = new Float32Array(msg.positions);
@@ -2840,9 +3128,11 @@ function NetworkGraph3DInner(props, ref) {
2840
3128
  );
2841
3129
  }
2842
3130
  }
3131
+ (_a = onLayoutTickRef.current) == null ? void 0 : _a.call(onLayoutTickRef, msg.alpha);
2843
3132
  }
2844
3133
  if (msg.type === "settled") {
2845
3134
  dataRef.current.settled = true;
3135
+ (_b = onLayoutSettledRef.current) == null ? void 0 : _b.call(onLayoutSettledRef);
2846
3136
  }
2847
3137
  };
2848
3138
  const layoutParams = resolveLayoutParams(nc, layoutProp);
@@ -2887,6 +3177,7 @@ function NetworkGraph3DInner(props, ref) {
2887
3177
  const d = dataRef.current;
2888
3178
  const theme = themeRef.current;
2889
3179
  const style = styleRef.current;
3180
+ highlightSetRef.current = highlightSet;
2890
3181
  applyNodeHighlight(
2891
3182
  gObj.nodesMesh,
2892
3183
  d.nodes,
@@ -2904,6 +3195,18 @@ function NetworkGraph3DInner(props, ref) {
2904
3195
  d.nodes.length,
2905
3196
  theme
2906
3197
  );
3198
+ if (sceneState.bloomPass) {
3199
+ const style2 = styleRef.current;
3200
+ if (highlightSet) {
3201
+ sceneState.bloomPass.strength = style2.bloomStrength * 1.8;
3202
+ sceneState.bloomPass.radius = style2.bloomRadius * 1.4;
3203
+ sceneState.bloomPass.threshold = 0.25;
3204
+ } else {
3205
+ sceneState.bloomPass.strength = style2.bloomStrength;
3206
+ sceneState.bloomPass.radius = style2.bloomRadius;
3207
+ sceneState.bloomPass.threshold = style2.bloomThreshold;
3208
+ }
3209
+ }
2907
3210
  const selectedNode = selectedNodeId ? d.nodes.find((n) => n.id === selectedNodeId) : null;
2908
3211
  updateSelectionRing(
2909
3212
  sceneState.selectionRing,
@@ -3051,13 +3354,11 @@ function NetworkGraph3DInner(props, ref) {
3051
3354
  var _a;
3052
3355
  return ((_a = sceneRef.current) == null ? void 0 : _a.camera) ?? null;
3053
3356
  },
3054
- async screenshot() {
3357
+ captureScreenshot() {
3055
3358
  const s = sceneRef.current;
3056
3359
  if (!s) return null;
3057
3360
  s.renderer.render(s.scene, s.camera);
3058
- return new Promise((resolve) => {
3059
- s.renderer.domElement.toBlob(resolve, "image/png");
3060
- });
3361
+ return s.renderer.domElement.toDataURL("image/png");
3061
3362
  }
3062
3363
  }));
3063
3364
  return /* @__PURE__ */ jsxRuntime.jsx(