@cocorof/graphier 1.4.0 → 1.4.2

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
@@ -174,6 +174,19 @@ export declare type LinkEventHandler = (link: GraphLink) => void;
174
174
 
175
175
  export declare const minimal: ThemeConfig;
176
176
 
177
+ export declare interface NavigationConfig {
178
+ /** What left-drag does (default: "rotate" in 3D, "pan" in 2D) */
179
+ leftButton?: "rotate" | "pan";
180
+ /** What right-drag does (default: "pan" in 3D, "rotate" in 2D) */
181
+ rightButton?: "rotate" | "pan";
182
+ /**
183
+ * Keyboard scheme — "pan": arrows/WASD pan + z/x zoom; "fly": thrust
184
+ * flight; "orbit": orbit around target; "off": disabled.
185
+ * (default: cameraMode in 3D, "pan" in 2D)
186
+ */
187
+ keyboard?: "fly" | "orbit" | "pan" | "off";
188
+ }
189
+
177
190
  export declare const neon: ThemeConfig;
178
191
 
179
192
  export declare const NetworkGraph3D: ForwardRefExoticComponent<NetworkGraph3DProps & RefAttributes<NetworkGraph3DRef>>;
@@ -230,6 +243,12 @@ export declare interface NetworkGraph3DProps {
230
243
  * simulation untouched. null/undefined shows all edges.
231
244
  */
232
245
  linkVisibility?: ((link: GraphLink) => boolean) | null;
246
+ /**
247
+ * Allow dragging individual nodes (default: true). Set false on dense
248
+ * graphs so left-drag always pans/orbits the camera instead of grabbing
249
+ * whichever node happens to be under the pointer.
250
+ */
251
+ enableNodeDrag?: boolean;
233
252
  /** Fly the camera to a node on single-click (default: true) */
234
253
  clickToFocus?: boolean;
235
254
  /** Highlight a node's neighborhood on hover when nothing is selected (default: false) */
@@ -335,6 +354,12 @@ export declare interface RendererConfig {
335
354
  * - "orbit": z/x zoom, arrow keys orbit around target
336
355
  */
337
356
  cameraMode?: "fly" | "orbit";
357
+ /**
358
+ * Pointer/keyboard navigation overrides. Defaults depend on layout
359
+ * dimensionality — 3D: left=rotate, right=pan, keyboard=cameraMode;
360
+ * 2D: left=pan, right=rotate, keyboard="pan".
361
+ */
362
+ navigation?: NavigationConfig;
338
363
  }
339
364
 
340
365
  export declare interface ResolvedTheme {
package/dist/index.js CHANGED
@@ -2031,6 +2031,45 @@ const _right = new THREE.Vector3();
2031
2031
  const _q = new THREE.Quaternion();
2032
2032
  const _dir = new THREE.Vector3();
2033
2033
  const _worldUp = new THREE.Vector3(0, 1, 0);
2034
+ const PAN_SPEED = 0.02;
2035
+ const PAN_ZOOM_FACTOR = 0.02;
2036
+ function createPanUpdate(camera, controls, keys) {
2037
+ const _panRight = new THREE.Vector3();
2038
+ const _panUp = new THREE.Vector3();
2039
+ const _panOffset = new THREE.Vector3();
2040
+ return function update() {
2041
+ let dx = 0;
2042
+ let dy = 0;
2043
+ if (keys["ArrowLeft"] || keys["a"] || keys["A"]) dx -= 1;
2044
+ if (keys["ArrowRight"] || keys["d"] || keys["D"]) dx += 1;
2045
+ if (keys["ArrowUp"] || keys["w"] || keys["W"]) dy += 1;
2046
+ if (keys["ArrowDown"] || keys["s"] || keys["S"]) dy -= 1;
2047
+ const zoomIn = keys["z"] || keys["Z"] || keys["+"] || keys["="];
2048
+ const zoomOut = keys["x"] || keys["X"] || keys["-"] || keys["_"];
2049
+ if (!dx && !dy && !zoomIn && !zoomOut) return false;
2050
+ const target = controls.target;
2051
+ const dist = camera.position.distanceTo(target);
2052
+ if (dx || dy) {
2053
+ const step = Math.max(dist, 50) * PAN_SPEED;
2054
+ _panRight.setFromMatrixColumn(camera.matrix, 0);
2055
+ _panUp.setFromMatrixColumn(camera.matrix, 1);
2056
+ _panOffset.set(0, 0, 0).addScaledVector(_panRight, dx * step).addScaledVector(_panUp, dy * step);
2057
+ camera.position.add(_panOffset);
2058
+ target.add(_panOffset);
2059
+ }
2060
+ if (zoomIn || zoomOut) {
2061
+ const factor = zoomIn ? -PAN_ZOOM_FACTOR : PAN_ZOOM_FACTOR;
2062
+ _panOffset.copy(camera.position).sub(target);
2063
+ const newLen = Math.max(
2064
+ controls.minDistance,
2065
+ Math.min(controls.maxDistance, _panOffset.length() * (1 + factor))
2066
+ );
2067
+ _panOffset.setLength(newLen);
2068
+ camera.position.copy(target).add(_panOffset);
2069
+ }
2070
+ return true;
2071
+ };
2072
+ }
2034
2073
  function createOrbitUpdate(camera, controls, keys) {
2035
2074
  return function update() {
2036
2075
  let zoom = 0;
@@ -2130,8 +2169,13 @@ function setupKeyboardControls(camera, controls, container, mode = "fly", flySpe
2130
2169
  container.addEventListener("keyup", onKeyUp);
2131
2170
  container.addEventListener("blur", onBlur);
2132
2171
  let enabled = true;
2133
- const innerUpdate = mode === "fly" ? createFlyUpdate(camera, controls, keys, state) : createOrbitUpdate(camera, controls, keys);
2134
- const updateFn = () => enabled ? innerUpdate() : false;
2172
+ let currentMode = mode;
2173
+ const updaters = {
2174
+ fly: createFlyUpdate(camera, controls, keys, state),
2175
+ orbit: createOrbitUpdate(camera, controls, keys),
2176
+ pan: createPanUpdate(camera, controls, keys)
2177
+ };
2178
+ const updateFn = () => enabled ? updaters[currentMode]() : false;
2135
2179
  function cleanup() {
2136
2180
  container.removeEventListener("keydown", onKeyDown2);
2137
2181
  container.removeEventListener("keyup", onKeyUp);
@@ -2144,7 +2188,11 @@ function setupKeyboardControls(camera, controls, container, mode = "fly", flySpe
2144
2188
  enabled = on;
2145
2189
  if (!on) for (const k of Object.keys(keys)) keys[k] = false;
2146
2190
  }
2147
- return { update: updateFn, cleanup, setFlySpeed, setEnabled };
2191
+ function setMode(m2) {
2192
+ currentMode = m2;
2193
+ for (const k of Object.keys(keys)) keys[k] = false;
2194
+ }
2195
+ return { update: updateFn, cleanup, setFlySpeed, setEnabled, setMode };
2148
2196
  }
2149
2197
  function createScene(container, backgroundColor, style, rendererConfig) {
2150
2198
  const scene = new THREE.Scene();
@@ -2184,7 +2232,10 @@ function createScene(container, backgroundColor, style, rendererConfig) {
2184
2232
  const cameraMode = (rendererConfig == null ? void 0 : rendererConfig.cameraMode) ?? "fly";
2185
2233
  const keyboard = setupKeyboardControls(camera, controls, container, cameraMode, style.flySpeed ?? 1);
2186
2234
  controls.enableZoom = false;
2187
- const flags = { is2D: false };
2235
+ const flags = {
2236
+ is2D: false,
2237
+ keyboardMode3D: cameraMode
2238
+ };
2188
2239
  const _wheelDir = new THREE.Vector3();
2189
2240
  const onWheel = (e) => {
2190
2241
  if (flags.is2D) return;
@@ -2215,33 +2266,38 @@ function createScene(container, backgroundColor, style, rendererConfig) {
2215
2266
  flags
2216
2267
  };
2217
2268
  }
2218
- function applyCameraMode2D(state, is2D) {
2269
+ const MOUSE_ACTION = {
2270
+ rotate: THREE.MOUSE.ROTATE,
2271
+ pan: THREE.MOUSE.PAN
2272
+ };
2273
+ function applyNavigationMode(state, is2D, nav) {
2219
2274
  const { controls, camera, keyboard } = state;
2220
2275
  state.flags.is2D = is2D;
2221
- controls.enableRotate = !is2D;
2276
+ const left = (nav == null ? void 0 : nav.leftButton) ?? (is2D ? "pan" : "rotate");
2277
+ const right = (nav == null ? void 0 : nav.rightButton) ?? (is2D ? "rotate" : "pan");
2278
+ controls.mouseButtons = {
2279
+ LEFT: MOUSE_ACTION[left],
2280
+ MIDDLE: THREE.MOUSE.DOLLY,
2281
+ RIGHT: MOUSE_ACTION[right]
2282
+ };
2283
+ controls.enableRotate = left === "rotate" || right === "rotate";
2284
+ controls.touches = left === "pan" ? { ONE: THREE.TOUCH.PAN, TWO: THREE.TOUCH.DOLLY_PAN } : { ONE: THREE.TOUCH.ROTATE, TWO: THREE.TOUCH.DOLLY_PAN };
2222
2285
  controls.enableZoom = is2D;
2223
2286
  controls.screenSpacePanning = is2D;
2224
- keyboard.setEnabled(!is2D);
2287
+ const kb = (nav == null ? void 0 : nav.keyboard) ?? (is2D ? "pan" : state.flags.keyboardMode3D);
2288
+ if (kb === "off") {
2289
+ keyboard.setEnabled(false);
2290
+ } else {
2291
+ keyboard.setEnabled(true);
2292
+ keyboard.setMode(kb);
2293
+ }
2225
2294
  if (is2D) {
2226
- controls.mouseButtons = {
2227
- LEFT: THREE.MOUSE.PAN,
2228
- MIDDLE: THREE.MOUSE.DOLLY,
2229
- RIGHT: THREE.MOUSE.PAN
2230
- };
2231
- controls.touches = { ONE: THREE.TOUCH.PAN, TWO: THREE.TOUCH.DOLLY_PAN };
2232
2295
  const t = controls.target;
2233
2296
  const dist = Math.max(camera.position.distanceTo(t), 50);
2234
2297
  t.z = 0;
2235
2298
  camera.up.set(0, 1, 0);
2236
2299
  camera.position.set(t.x, t.y, dist);
2237
2300
  camera.lookAt(t);
2238
- } else {
2239
- controls.mouseButtons = {
2240
- LEFT: THREE.MOUSE.ROTATE,
2241
- MIDDLE: THREE.MOUSE.DOLLY,
2242
- RIGHT: THREE.MOUSE.PAN
2243
- };
2244
- controls.touches = { ONE: THREE.TOUCH.ROTATE, TWO: THREE.TOUCH.DOLLY_PAN };
2245
2301
  }
2246
2302
  controls.update();
2247
2303
  }
@@ -2604,11 +2660,12 @@ function setupInteraction(canvas, camera, controls, getNodesMesh, getNodes, call
2604
2660
  const dragPlane = new THREE.Plane();
2605
2661
  const dragIntersect = new THREE.Vector3();
2606
2662
  function onPointerDown2(e) {
2663
+ var _a;
2607
2664
  mouseDownPos = { x: e.clientX, y: e.clientY };
2608
2665
  if (container && document.activeElement !== container) {
2609
2666
  container.focus({ preventScroll: true });
2610
2667
  }
2611
- if (callbacks.onNodeDrag) {
2668
+ if (callbacks.onNodeDrag && (((_a = options == null ? void 0 : options.getNodeDragEnabled) == null ? void 0 : _a.call(options)) ?? true)) {
2612
2669
  const nodesMesh = getNodesMesh();
2613
2670
  if (nodesMesh) {
2614
2671
  const rect = canvas.getBoundingClientRect();
@@ -2920,6 +2977,7 @@ function NetworkGraph3DInner(props, ref) {
2920
2977
  nodeValueAccessor,
2921
2978
  visibleNodeIds,
2922
2979
  linkVisibility,
2980
+ enableNodeDrag = true,
2923
2981
  clickToFocus = true,
2924
2982
  hoverHighlight = false,
2925
2983
  hoverHighlightHops = 1
@@ -2960,6 +3018,7 @@ function NetworkGraph3DInner(props, ref) {
2960
3018
  const labelFormatterRef = useRef(labelFormatter);
2961
3019
  const highlightSetRef = useRef(null);
2962
3020
  const clickToFocusRef = useRef(clickToFocus);
3021
+ const enableNodeDragRef = useRef(enableNodeDrag);
2963
3022
  const hoverHighlightRef = useRef(hoverHighlight);
2964
3023
  const hoverHighlightHopsRef = useRef(hoverHighlightHops);
2965
3024
  const adjacencyMapRef = useRef(/* @__PURE__ */ new Map());
@@ -2981,6 +3040,7 @@ function NetworkGraph3DInner(props, ref) {
2981
3040
  selectedNodeIdRef.current = selectedNodeId;
2982
3041
  labelFormatterRef.current = labelFormatter;
2983
3042
  clickToFocusRef.current = clickToFocus;
3043
+ enableNodeDragRef.current = enableNodeDrag;
2984
3044
  hoverHighlightRef.current = hoverHighlight;
2985
3045
  hoverHighlightHopsRef.current = hoverHighlightHops;
2986
3046
  const is2D = (layoutProp == null ? void 0 : layoutProp.dimensions) === 2;
@@ -3163,7 +3223,8 @@ function NetworkGraph3DInner(props, ref) {
3163
3223
  },
3164
3224
  {
3165
3225
  getClickToFocus: () => clickToFocusRef.current,
3166
- getIs2D: () => is2DRef.current
3226
+ getIs2D: () => is2DRef.current,
3227
+ getNodeDragEnabled: () => enableNodeDragRef.current
3167
3228
  }
3168
3229
  );
3169
3230
  return () => {
@@ -3443,10 +3504,11 @@ function NetworkGraph3DInner(props, ref) {
3443
3504
  );
3444
3505
  }
3445
3506
  }, [visibleSet, linkVisibility, mergedData]);
3507
+ const navKey = JSON.stringify((rendererProp == null ? void 0 : rendererProp.navigation) ?? null);
3446
3508
  useEffect(() => {
3447
3509
  const s = sceneRef.current;
3448
- if (s) applyCameraMode2D(s, is2D);
3449
- }, [is2D]);
3510
+ if (s) applyNavigationMode(s, is2D, rendererProp == null ? void 0 : rendererProp.navigation);
3511
+ }, [is2D, navKey]);
3450
3512
  useEffect(() => {
3451
3513
  const gObj = graphObjRef.current;
3452
3514
  const sceneState = sceneRef.current;
@@ -3698,11 +3760,12 @@ function NetworkGraph3DInner(props, ref) {
3698
3760
  const s = sceneRef.current;
3699
3761
  if (!s) return null;
3700
3762
  const cam = s.camera;
3701
- const dist = Math.abs(cam.position.z);
3763
+ const t = s.controls.target;
3764
+ const dist = Math.max(cam.position.distanceTo(t), 1);
3702
3765
  const halfH = Math.tan(cam.fov * Math.PI / 360) * dist;
3703
3766
  return {
3704
- cx: cam.position.x,
3705
- cy: cam.position.y,
3767
+ cx: t.x,
3768
+ cy: t.y,
3706
3769
  halfW: halfH * cam.aspect,
3707
3770
  halfH
3708
3771
  };