@loradb/lora-graph-canvas 0.11.0 → 0.11.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.
@@ -4,6 +4,25 @@ export interface UnifiedEngine<N extends NodeObject = NodeObject, L extends Link
4
4
  /** Switch presentation mode in place. Animates the camera + the
5
5
  * per-node z constraints; does not destroy the engine. */
6
6
  setMode(mode: GraphMode, durationMs?: number): void;
7
+ /** First-load reveal that runs concurrently with the force
8
+ * simulation: every animation frame, recompute the bbox-fitted
9
+ * pose and ease the camera toward it via a critically-damped
10
+ * spring. The spring's time constant is tuned by node count
11
+ * (small graphs settle in ~200 ms; large graphs ease over ~900 ms
12
+ * so we don't over-react to early ticks where the bbox is still
13
+ * exploding outward). Cancels on user interaction, on
14
+ * `onEngineStop`, or after `maxDurationMs`. Padding is in CSS
15
+ * pixels and reserved on every side of the viewport.
16
+ *
17
+ * Use on initial mount only — calling this on a graph the user
18
+ * has already explored will yank their camera. */
19
+ introFollow(opts?: {
20
+ padding?: number;
21
+ maxDurationMs?: number;
22
+ /** Optional time-constant override (seconds). If omitted, derived
23
+ * from node count via a perf-tier-style log scale. */
24
+ tauSeconds?: number;
25
+ }): void;
7
26
  }
8
27
  export declare function createEngineUnified<N extends NodeObject, L extends LinkObject>(mount: HTMLElement, opts: CreateEngineOptions<N, L> & {
9
28
  initialMode: GraphMode;
@@ -9,6 +9,13 @@
9
9
  * Returns null when running in an environment without
10
10
  * `requestAnimationFrame` (jsdom in unit tests). */
11
11
  export declare function runAnim(durationMs: number, step: (t: number) => void, onDone?: () => void, ease?: (t: number) => number): () => void;
12
+ /** Open-ended RAF loop. Unlike `runAnim` there's no fixed duration —
13
+ * `step(dt)` receives the seconds elapsed since the previous frame
14
+ * (capped at 1/30 s so a tab-switch doesn't deliver a giant dt that
15
+ * blows past spring targets) and decides for itself when it has
16
+ * converged. Returning `true` from `step` stops the loop the same way
17
+ * the returned `cancel()` would. */
18
+ export declare function runFollow(step: (dtSeconds: number) => boolean | void): () => void;
12
19
  export declare function easeOutQuad(t: number): number;
13
20
  /** Smoother S-curve — slow start, fast middle, slow end. Reads as
14
21
  * more "cinematic" than easeOutQuad for longer cross-mode camera
@@ -70,6 +70,31 @@ export interface GraphEngine<N extends NodeObject = NodeObject, L extends LinkOb
70
70
  zoom?: number;
71
71
  durationMs?: number;
72
72
  }): void;
73
+ /** Translate the view by world-space delta. Moves camera AND
74
+ * lookAt by the same vector so the orbit / view direction is
75
+ * preserved (a true "pan" rather than an orbit step). In 2D the
76
+ * z component is ignored — the top-down camera is locked to a
77
+ * constant height. */
78
+ panBy(delta: {
79
+ x?: number;
80
+ y?: number;
81
+ z?: number;
82
+ }, durationMs?: number): void;
83
+ /** Jump the view to a world coordinate, preserving the current
84
+ * viewing direction. Differs from `focusOn` in that it accepts a
85
+ * raw coordinate rather than a node-style target and doesn't
86
+ * re-tighten the zoom. Useful for "go to coordinates" UI. */
87
+ goTo(target: {
88
+ x: number;
89
+ y: number;
90
+ z?: number;
91
+ }, opts?: {
92
+ durationMs?: number;
93
+ }): void;
94
+ /** Fit the camera to a subset of nodes. Same camera math as `fit()`
95
+ * but the bbox is computed over `nodeIds` instead of the whole
96
+ * graph. Falls back to a full fit when `nodeIds` is empty. */
97
+ fitToNodes(nodeIds: ReadonlyArray<string | number>, durationMs?: number, padding?: number): void;
73
98
  /** Snapshot the current camera so it can be restored later. */
74
99
  getCameraState(): CameraState;
75
100
  /** Restore a snapshot produced by `getCameraState`. */
@@ -7,7 +7,7 @@ import { SelectionApi } from './useGraphSelection';
7
7
  export interface UseGraphClipboardParams<N extends NodeObject, L extends LinkObject> {
8
8
  enableClipboard: boolean;
9
9
  dataApi: GraphDataApi<N, L>;
10
- deleteGate: GraphDeleteGateApi<N, L>;
10
+ deleteGate: GraphDeleteGateApi<L>;
11
11
  selection: SelectionApi;
12
12
  setSelectedLinkIds: React.Dispatch<React.SetStateAction<Array<string | number>>>;
13
13
  engineRef: MutableRefObject<GraphEngine<N, L> | null>;
@@ -24,7 +24,7 @@ export interface GraphDataApi<N extends NodeObject, L extends LinkObject> {
24
24
  }>): N[];
25
25
  updateNode(id: string | number, patch: Partial<N>): void;
26
26
  removeNode(id: string | number): void;
27
- removeNodes(ids: Array<string | number>): void;
27
+ removeNodes(ids: ReadonlyArray<string | number>): void;
28
28
  addLink(link: {
29
29
  source: string | number;
30
30
  target: string | number;
@@ -12,27 +12,27 @@ export interface UseGraphDeleteGateParams<N extends NodeObject, L extends LinkOb
12
12
  }) => void;
13
13
  /** Called after a successful node delete so the caller can clear its
14
14
  * own selection / hover state. Skipped if the guard rejected. */
15
- afterNodeDelete?: (ids: Array<string | number>) => void;
16
- afterLinkDelete?: (ids: Array<string | number>) => void;
15
+ afterNodeDelete?: (ids: ReadonlyArray<string | number>) => void;
16
+ afterLinkDelete?: (ids: ReadonlyArray<string | number>) => void;
17
17
  }
18
- export interface GraphDeleteGateApi<N extends NodeObject, L extends LinkObject> {
18
+ export interface GraphDeleteGateApi<L extends LinkObject> {
19
19
  /** Resolves the selected node ids against current data, runs the guard,
20
20
  * and removes them. Returns `false` if the guard rejected or nothing
21
21
  * matched. */
22
- requestNodeDelete: (ids: Array<string | number>, source: DeletionSource) => Promise<boolean>;
22
+ requestNodeDelete: (ids: ReadonlyArray<string | number>, source: DeletionSource) => Promise<boolean>;
23
23
  /** Same, for links. Accepts either an id list or a predicate so context
24
24
  * menus that hold the link reference can still target it precisely
25
25
  * (links sometimes lack an id). */
26
- requestLinkDelete: (target: Array<string | number> | ((l: L) => boolean), source: DeletionSource) => Promise<boolean>;
26
+ requestLinkDelete: (target: ReadonlyArray<string | number> | ((l: L) => boolean), source: DeletionSource) => Promise<boolean>;
27
27
  /** Convenience: run node + link guards in sequence. Used by the
28
28
  * "delete selection" sites (toolbar / selection panel / keyboard)
29
29
  * where a mixed selection is common. Each guard fires independently;
30
30
  * rejecting one doesn't cancel the other. Returns true if anything
31
31
  * was actually deleted. */
32
- requestMixedDelete: (nodeIds: Array<string | number>, linkIds: Array<string | number>, source: DeletionSource) => Promise<boolean>;
32
+ requestMixedDelete: (nodeIds: ReadonlyArray<string | number>, linkIds: ReadonlyArray<string | number>, source: DeletionSource) => Promise<boolean>;
33
33
  }
34
34
  /** Single chokepoint for every gated delete in the canvas. Centralising
35
35
  * here keeps the guard semantics (batched calls, post-delete callbacks,
36
36
  * selection cleanup) consistent across keyboard, toolbar, context menu,
37
37
  * selection panel, and imperative paths. */
38
- export declare function useGraphDeleteGate<N extends NodeObject, L extends LinkObject>(params: UseGraphDeleteGateParams<N, L>): GraphDeleteGateApi<N, L>;
38
+ export declare function useGraphDeleteGate<N extends NodeObject, L extends LinkObject>(params: UseGraphDeleteGateParams<N, L>): GraphDeleteGateApi<L>;
@@ -1,4 +1,4 @@
1
- import { GraphEngine } from '../engines/types';
1
+ import { UnifiedEngine } from '../engines/createEngineUnified';
2
2
  import { GraphData, GraphMode, LinkObject, LoraGraphCanvasProps, NodeObject } from '../types';
3
3
  interface UseGraphEngineParams<N extends NodeObject, L extends LinkObject> {
4
4
  mount: HTMLElement | null;
@@ -19,5 +19,5 @@ interface UseGraphEngineParams<N extends NodeObject, L extends LinkObject> {
19
19
  * to 0, top-down camera) and 3D (z released, orbit camera). The
20
20
  * engine is destroyed only when the host element changes or the
21
21
  * component unmounts. */
22
- export declare function useGraphEngine<N extends NodeObject, L extends LinkObject>(params: UseGraphEngineParams<N, L>): GraphEngine<N, L> | null;
22
+ export declare function useGraphEngine<N extends NodeObject, L extends LinkObject>(params: UseGraphEngineParams<N, L>): UnifiedEngine<N, L> | null;
23
23
  export {};
@@ -7,7 +7,7 @@ import { SelectionApi } from './useGraphSelection';
7
7
  export interface UseGraphKeybindingsParams<N extends NodeObject, L extends LinkObject> {
8
8
  engine: GraphEngine<N, L> | null;
9
9
  dataApi: GraphDataApi<N, L>;
10
- deleteGate: GraphDeleteGateApi<N, L>;
10
+ deleteGate: GraphDeleteGateApi<L>;
11
11
  selection: SelectionApi;
12
12
  mode: GraphMode;
13
13
  setMode: (next: GraphMode) => void;
@@ -3,12 +3,21 @@ export interface UseGraphSelectionOptions {
3
3
  onChange?: (selectedIds: Array<string | number>) => void;
4
4
  }
5
5
  export interface SelectionApi {
6
- selected: Array<string | number>;
6
+ /** Selected ids as a stable array. Identity flips on every mutation
7
+ * so it can drive React effect dep arrays. Backed by the same `Set`
8
+ * as `selectedSet`. */
9
+ selected: ReadonlyArray<string | number>;
10
+ /** O(1)-membership view of the same selection. Wrappers that test
11
+ * `selectedSet.has(id)` on every node/link must read this instead
12
+ * of the array — `selected.includes(id)` is O(N) and on a 10k
13
+ * selection the kapsule digest paid that cost per node per
14
+ * frame. */
15
+ selectedSet: ReadonlySet<string | number>;
7
16
  isSelected(id: string | number): boolean;
8
17
  toggle(id: string | number, opts?: {
9
18
  additive?: boolean;
10
19
  }): void;
11
- set(ids: Array<string | number>): void;
20
+ set(ids: ReadonlyArray<string | number> | ReadonlySet<string | number>): void;
12
21
  clear(): void;
13
22
  }
14
23
  export declare function useGraphSelection(opts: UseGraphSelectionOptions): SelectionApi;
@@ -7,7 +7,7 @@ import { GraphClipboardApi } from './useGraphClipboard';
7
7
  export interface UseImperativeGraphHandleParams<N extends NodeObject, L extends LinkObject> {
8
8
  ref: React.Ref<LoraGraphCanvasHandle<N, L>>;
9
9
  dataApi: GraphDataApi<N, L>;
10
- deleteGate: GraphDeleteGateApi<N, L>;
10
+ deleteGate: GraphDeleteGateApi<L>;
11
11
  selection: SelectionApi;
12
12
  engine: GraphEngine<N, L> | null;
13
13
  mode: GraphMode;
@@ -17,8 +17,18 @@ export interface UseImperativeGraphHandleParams<N extends NodeObject, L extends
17
17
  exportJSON: () => string;
18
18
  importJSON: (json: string) => void;
19
19
  downloadJSON: (filename?: string) => void;
20
+ /** Live link selection — needed for fitToSelection to expand into
21
+ * link endpoints. */
22
+ selectedLinkIds: Array<string | number>;
20
23
  }
21
24
  /** Hooks `useImperativeHandle` to expose the canvas's full
22
25
  * programmatic API surface to consumers via a forwardRef. Kept as a
23
- * hook so the main component file stays focused on rendering. */
26
+ * hook so the main component file stays focused on rendering.
27
+ *
28
+ * Live state (selection, link selection, data, engine) is read inside
29
+ * the handle methods via a ref so the handle object identity doesn't
30
+ * churn on every click. Without this, every selection change rebuilt
31
+ * the whole handle and any host holding `ref.current` saw a fresh set
32
+ * of method identities each click — defeating downstream memoisation
33
+ * on the host side. */
24
34
  export declare function useImperativeGraphHandle<N extends NodeObject, L extends LinkObject>(params: UseImperativeGraphHandleParams<N, L>): void;